Search Nukepedia's 2300+ free Nuke tools catalog. Includes: - scrape.py: build catalog from nukepedia.com (rate-limited) - search.py: query by name, category, rating, author - Pre-scraped catalog with 2341 tools Categories: gizmos, python, plugins, toolsets, blink, hiero, etc. Support Nukepedia: https://nukepedia.com/donate
172 lines
5.5 KiB
Python
172 lines
5.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Search the Nukepedia tool catalog.
|
|
|
|
Usage:
|
|
python search.py <query> [options]
|
|
|
|
Examples:
|
|
python search.py keyer
|
|
python search.py edge --category gizmos
|
|
python search.py "" --category python --min-rating 4
|
|
python search.py tracker --verbose
|
|
|
|
Support Nukepedia: https://nukepedia.com/donate
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import math
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
|
|
def load_catalog(path: Path) -> dict:
|
|
with open(path) as f:
|
|
return json.load(f)
|
|
|
|
|
|
def search(catalog: dict, query: str = "", category: str = None,
|
|
subcategory: str = None, author: str = None,
|
|
min_rating: float = None, min_downloads: int = None,
|
|
limit: int = 10) -> list:
|
|
"""Search tools matching criteria."""
|
|
results = []
|
|
query_lower = query.lower() if query else ""
|
|
|
|
for tool in catalog.get("tools", []):
|
|
# text search
|
|
if query_lower:
|
|
searchable = f"{tool['name']} {tool['description']} {tool['author']} {tool['subcategory']}".lower()
|
|
if query_lower not in searchable:
|
|
continue
|
|
|
|
# category filter
|
|
if category and tool["category"].lower() != category.lower():
|
|
continue
|
|
|
|
# subcategory filter
|
|
if subcategory and subcategory.lower() not in tool["subcategory"].lower():
|
|
continue
|
|
|
|
# author filter
|
|
if author and author.lower() not in tool["author"].lower():
|
|
continue
|
|
|
|
# rating filter
|
|
if min_rating is not None:
|
|
if tool["rating"] is None or tool["rating"] < min_rating:
|
|
continue
|
|
|
|
# downloads filter
|
|
if min_downloads is not None:
|
|
if tool["downloads"] < min_downloads:
|
|
continue
|
|
|
|
results.append(tool)
|
|
|
|
# sort by relevance
|
|
def score(t):
|
|
s = 0.0
|
|
if query_lower:
|
|
if t["name"].lower() == query_lower:
|
|
s += 100
|
|
elif t["name"].lower().startswith(query_lower):
|
|
s += 50
|
|
elif query_lower in t["name"].lower():
|
|
s += 25
|
|
if query_lower in t["description"].lower():
|
|
s += 10
|
|
if t["rating"]:
|
|
s += t["rating"] * 5
|
|
if t["downloads"] > 0:
|
|
s += math.log10(t["downloads"]) * 2
|
|
return s
|
|
|
|
results.sort(key=score, reverse=True)
|
|
return results[:limit] if limit else results
|
|
|
|
|
|
def format_brief(tool: dict) -> str:
|
|
rating = f" ({tool['rating']:.1f}/5)" if tool["rating"] else ""
|
|
desc = tool["description"][:80] + "..." if len(tool["description"]) > 80 else tool["description"]
|
|
return f" {tool['name']} - {tool['category'].title()}{rating}\n {desc}\n {tool['url']}"
|
|
|
|
|
|
def format_verbose(tool: dict) -> str:
|
|
lines = [
|
|
f"\n {tool['name']}",
|
|
f" {'=' * len(tool['name'])}",
|
|
f" Category: {tool['category'].title()} / {tool['subcategory']}",
|
|
f" Author: {tool['author'] or 'Unknown'}",
|
|
]
|
|
if tool["rating"]:
|
|
lines.append(f" Rating: {tool['rating']:.1f}/5 ({tool['rating_count']} votes)")
|
|
lines.append(f" Downloads: {tool['downloads']}")
|
|
if tool["nuke_versions"]:
|
|
lines.append(f" Nuke versions: {tool['nuke_versions']}")
|
|
if tool["platforms"]:
|
|
lines.append(f" Platforms: {', '.join(p.title() for p in tool['platforms'])}")
|
|
if tool["license"]:
|
|
lines.append(f" License: {tool['license']}")
|
|
lines.append(f" URL: {tool['url']}")
|
|
if tool["description"]:
|
|
lines.append(f"\n {tool['description']}")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Search Nukepedia tool catalog")
|
|
parser.add_argument("query", nargs="?", default="", help="Search query")
|
|
parser.add_argument("--category", "-c", help="Filter by category")
|
|
parser.add_argument("--subcategory", "-s", help="Filter by subcategory")
|
|
parser.add_argument("--author", "-a", help="Filter by author")
|
|
parser.add_argument("--min-rating", type=float, help="Minimum rating (1-5)")
|
|
parser.add_argument("--min-downloads", type=int, help="Minimum downloads")
|
|
parser.add_argument("--limit", "-l", type=int, default=10, help="Max results")
|
|
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
|
|
parser.add_argument("--catalog", default=None, help="Path to catalog JSON")
|
|
parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
args = parser.parse_args()
|
|
|
|
catalog_path = Path(args.catalog) if args.catalog else Path(__file__).parent.parent / "data" / "nukepedia-catalog.json"
|
|
|
|
if not catalog_path.exists():
|
|
print(f"Catalog not found: {catalog_path}")
|
|
print("Run scrape.py first to build the catalog.")
|
|
sys.exit(1)
|
|
|
|
catalog = load_catalog(catalog_path)
|
|
results = search(
|
|
catalog,
|
|
query=args.query,
|
|
category=args.category,
|
|
subcategory=args.subcategory,
|
|
author=args.author,
|
|
min_rating=args.min_rating,
|
|
min_downloads=args.min_downloads,
|
|
limit=args.limit
|
|
)
|
|
|
|
if args.json:
|
|
print(json.dumps(results, indent=2))
|
|
return
|
|
|
|
if not results:
|
|
print("No tools found matching your query.")
|
|
return
|
|
|
|
for tool in results:
|
|
if args.verbose:
|
|
print(format_verbose(tool))
|
|
else:
|
|
print(format_brief(tool))
|
|
print()
|
|
|
|
print(f"{len(results)} tool(s) found. Support Nukepedia: https://nukepedia.com/donate")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|