openfree's picture
Update app.py
1d55cbc verified
raw
history blame
61.3 kB
import os
import random
import base64
import requests
import tempfile
import shutil
import time
import numpy as np
import traceback
from typing import List, Tuple
from datetime import datetime, timedelta
from pathlib import Path
from io import BytesIO
from urllib.parse import urljoin
# Selenium ๊ด€๋ จ
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import WebDriverException, TimeoutException
# ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ
from PIL import Image, ImageDraw, ImageFont
# Gradio
import gradio as gr
# HuggingFace
from huggingface_hub import InferenceClient
from dotenv import load_dotenv
# HTML ํŒŒ์‹ฑ
from bs4 import BeautifulSoup
# ์Œ์„ฑ ๋ฐ ๋น„๋””์˜ค ์ฒ˜๋ฆฌ
from gtts import gTTS
from moviepy.editor import (
VideoFileClip,
AudioFileClip,
ImageClip,
concatenate_videoclips
)
# ์ƒ๋‹จ์— import ์ถ”๊ฐ€
import textwrap
# .env ํŒŒ์ผ์—์„œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ
load_dotenv()
# HuggingFace ์ธํผ๋Ÿฐ์Šค ํด๋ผ์ด์–ธํŠธ ์„ค์ •
hf_client = InferenceClient(
"CohereForAI/c4ai-command-r-plus-08-2024",
token=os.getenv("HF_TOKEN")
)
# ์Šคํฌ๋ฆฐ์ƒท ์บ์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ •
CACHE_DIR = Path("screenshot_cache")
CACHE_DIR.mkdir(exist_ok=True)
# ์ „์—ญ ๋ณ€์ˆ˜๋กœ ์Šคํฌ๋ฆฐ์ƒท ์บ์‹œ ์„ ์–ธ
SCREENSHOT_CACHE = {}
def get_cached_screenshot(url: str) -> str:
"""์บ์‹œ๋œ ์Šคํฌ๋ฆฐ์ƒท ๊ฐ€์ ธ์˜ค๊ธฐ ๋˜๋Š” ์ƒˆ๋กœ ์ƒ์„ฑ"""
try:
# URL์„ ์•ˆ์ „ํ•œ ํŒŒ์ผ๋ช…์œผ๋กœ ๋ณ€ํ™˜
safe_filename = base64.urlsafe_b64encode(url.encode()).decode()
cache_file = CACHE_DIR / f"{safe_filename[:200]}.jpg" # PNG ๋Œ€์‹  JPG ์‚ฌ์šฉ
if cache_file.exists():
try:
with Image.open(cache_file) as img:
buffered = BytesIO()
img.save(buffered, format="JPEG", quality=85, optimize=True)
return base64.b64encode(buffered.getvalue()).decode()
except Exception as e:
print(f"Cache read error for {url}: {e}")
if cache_file.exists():
cache_file.unlink()
return take_screenshot(url)
except Exception as e:
print(f"Screenshot cache error for {url}: {e}")
return ""
def take_screenshot(url: str) -> str:
"""์›น์‚ฌ์ดํŠธ ์Šคํฌ๋ฆฐ์ƒท ์ดฌ์˜"""
if not url.startswith('http'):
url = f"https://{url}"
options = webdriver.ChromeOptions()
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--window-size=1080,720')
driver = None
try:
driver = webdriver.Chrome(options=options)
driver.get(url)
# ํŽ˜์ด์ง€ ๋กœ๋”ฉ ๋Œ€๊ธฐ
WebDriverWait(driver, 15).until(
EC.presence_of_element_located((By.TAG_NAME, "body"))
)
# ์ถ”๊ฐ€ ๋Œ€๊ธฐ ์‹œ๊ฐ„
time.sleep(3)
# ์Šคํฌ๋ฆฐ์ƒท ์ดฌ์˜ ๋ฐ ์ตœ์ ํ™”
screenshot = driver.get_screenshot_as_png()
img = Image.open(BytesIO(screenshot))
# ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์ตœ์ ํ™”
max_size = (800, 600)
img.thumbnail(max_size, Image.Resampling.LANCZOS)
# JPEG๋กœ ๋ณ€ํ™˜ ๋ฐ ์ตœ์ ํ™”
if img.mode in ('RGBA', 'LA'):
background = Image.new('RGB', img.size, (255, 255, 255))
background.paste(img, mask=img.split()[-1])
img = background
# ์บ์‹œ ์ €์žฅ
safe_filename = base64.urlsafe_b64encode(url.encode()).decode()
cache_file = CACHE_DIR / f"{safe_filename[:200]}.jpg"
img.save(cache_file, format="JPEG", quality=85, optimize=True)
# ๋ฐ˜ํ™˜์šฉ ์ด๋ฏธ์ง€ ์ƒ์„ฑ
buffered = BytesIO()
img.save(buffered, format="JPEG", quality=85, optimize=True)
return base64.b64encode(buffered.getvalue()).decode()
except Exception as e:
print(f"Screenshot error for {url}: {e}")
return ""
finally:
if driver:
driver.quit()
def cleanup_cache():
"""์บ์‹œ ์ •๋ฆฌ"""
try:
current_time = time.time()
for cache_file in CACHE_DIR.glob("*.jpg"):
try:
# 24์‹œ๊ฐ„ ์ด์ƒ ๋œ ํŒŒ์ผ ๋˜๋Š” 0๋ฐ”์ดํŠธ ํŒŒ์ผ ์‚ญ์ œ
if (current_time - cache_file.stat().st_mtime > 86400) or cache_file.stat().st_size == 0:
cache_file.unlink()
except Exception as e:
print(f"Error cleaning cache file {cache_file}: {e}")
except Exception as e:
print(f"Cache cleanup error: {e}")
# ์•ฑ ์‹œ์ž‘ ์‹œ ์บ์‹œ ์ •๋ฆฌ
cleanup_cache()
def calculate_rising_rate(created_date: str, rank: int) -> int:
"""AI Rising Rate ๊ณ„์‚ฐ"""
# ์ƒ์„ฑ์ผ ๊ธฐ์ค€ ์ ์ˆ˜ ๊ณ„์‚ฐ
created = datetime.strptime(created_date.split('T')[0], '%Y-%m-%d')
today = datetime.now()
days_diff = (today - created).days
date_score = max(0, 300 - days_diff) # ์ตœ๋Œ€ 300์ 
# ์ˆœ์œ„ ๊ธฐ์ค€ ์ ์ˆ˜ ๊ณ„์‚ฐ
rank_score = max(0, 600 - rank) # ์ตœ๋Œ€ 300์ 
# ์ด์  ๊ณ„์‚ฐ
total_score = date_score + rank_score
# ๋ณ„ ๊ฐœ์ˆ˜ ๊ณ„์‚ฐ (0~5)
if total_score <= 200:
stars = 1
elif total_score <= 400:
stars = 2
elif total_score <= 600:
stars = 3
elif total_score <= 800:
stars = 4
else:
stars = 5
return stars
def get_popularity_grade(likes: int, stars: int) -> tuple:
"""AI Popularity Score ๋“ฑ๊ธ‰ ๊ณ„์‚ฐ"""
# ๊ธฐ๋ณธ ์ ์ˆ˜ (likes)
base_score = min(likes, 10000) # ์ตœ๋Œ€ 10000์ 
# ๋ณ„์  ์ถ”๊ฐ€ ์ ์ˆ˜ (๋ณ„ ํ•˜๋‚˜๋‹น 500์ )
star_score = stars * 1000
# ์ด์ 
total_score = base_score + star_score
# ๋“ฑ๊ธ‰ ํ…Œ์ด๋ธ” (18๋‹จ๊ณ„)
grades = [
(14500, "AAA+"), (14000, "AAA"), (13500, "AAA-"),
(13000, "AA+"), (12500, "AA"), (12000, "AA-"),
(11500, "A+"), (11000, "A"), (10000, "A-"),
(9000, "BBB+"), (8000, "BBB"), (7000, "BBB-"),
(6000, "BB+"), (5000, "BB"), (4000, "BB-"),
(3000, "B+"), (2000, "B"), (1000, "B-")
]
for threshold, grade in grades:
if total_score >= threshold:
return grade, total_score
return "B-", total_score
# get_card ํ•จ์ˆ˜ ๋‚ด์˜ hardware_info ๋ถ€๋ถ„์„ ๋‹ค์Œ์œผ๋กœ ๊ต์ฒด:
def get_rating_info(item: dict, index: int) -> str:
"""ํ‰๊ฐ€ ์ •๋ณด HTML ์ƒ์„ฑ"""
created = item.get('createdAt', '').split('T')[0]
likes = int(str(item.get('likes', '0')).replace(',', ''))
# AI Rising Rate ๊ณ„์‚ฐ
stars = calculate_rising_rate(created, index + 1)
star_html = "โ˜…" * stars + "โ˜†" * (5 - stars) # ์ฑ„์›Œ์ง„ ๋ณ„๊ณผ ๋นˆ ๋ณ„ ์กฐํ•ฉ
# AI Popularity Score ๊ณ„์‚ฐ
grade, score = get_popularity_grade(likes, stars)
# ๋“ฑ๊ธ‰๋ณ„ ์ƒ‰์ƒ ์„ค์ •
grade_colors = {
'AAA': '#FFD700', 'AA': '#FFA500', 'A': '#FF4500',
'BBB': '#4169E1', 'BB': '#1E90FF', 'B': '#00BFFF'
}
grade_base = grade.rstrip('+-')
grade_color = grade_colors.get(grade_base, '#666666')
return f"""
<div style='
margin-top: 15px;
padding: 15px;
background: rgba(255,255,255,0.4);
border-radius: 10px;
font-size: 0.9em;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);'>
<div style='
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;'>
<div style='
color: #333;
display: flex;
flex-direction: column;
gap: 5px;'>
<span style='font-weight: bold;'>AI Rising Rate:</span>
<span style='
color: #FF8C00;
font-size: 1.4em;
letter-spacing: 2px;
text-shadow: 1px 1px 2px rgba(0,0,0,0.1);'>{star_html}</span>
</div>
<div style='
color: #333;
display: flex;
flex-direction: column;
gap: 5px;'>
<span style='font-weight: bold;'>AI Popularity Score:</span>
<span style='
font-size: 1.2em;
font-weight: bold;
color: {grade_color};
text-shadow: 1px 1px 2px rgba(0,0,0,0.1);'>{grade} ({score:,})</span>
</div>
</div>
</div>
"""
def get_hardware_info(item: dict) -> tuple:
"""ํ•˜๋“œ์›จ์–ด ์ •๋ณด ์ถ”์ถœ"""
try:
# runtime ์ •๋ณด ํ™•์ธ
runtime = item.get('runtime', {})
# CPU ์ •๋ณด ์ฒ˜๋ฆฌ
cpu_info = runtime.get('cpu', 'Standard')
# GPU ์ •๋ณด ์ฒ˜๋ฆฌ
gpu_info = "None"
if runtime.get('accelerator') == "gpu":
gpu_type = runtime.get('gpu', {}).get('name', '')
gpu_memory = runtime.get('gpu', {}).get('memory', '')
if gpu_type:
gpu_info = f"{gpu_type}"
if gpu_memory:
gpu_info += f" ({gpu_memory}GB)"
# spaces decorator ํ™•์ธ
if '@spaces.GPU' in str(item.get('sdk_version', '')):
if gpu_info == "None":
gpu_info = "GPU Enabled"
# SDK ์ •๋ณด ์ฒ˜๋ฆฌ
sdk = item.get('sdk', 'N/A')
print(f"Debug - Runtime Info: {runtime}") # ๋””๋ฒ„๊ทธ ์ถœ๋ ฅ
print(f"Debug - GPU Info: {gpu_info}") # ๋””๋ฒ„๊ทธ ์ถœ๋ ฅ
return cpu_info, gpu_info, sdk
except Exception as e:
print(f"Error parsing hardware info: {str(e)}")
return 'Standard', 'None', 'N/A'
def get_card(item: dict, index: int, card_type: str = "space") -> str:
"""ํ†ตํ•ฉ ์นด๋“œ HTML ์ƒ์„ฑ"""
item_id = item.get('id', '')
author, title = item_id.split('/', 1)
likes = format(item.get('likes', 0), ',')
created = item.get('createdAt', '').split('T')[0]
# short_description ๊ฐ€์ ธ์˜ค๊ธฐ
short_description = item.get('cardData', {}).get('short_description', '')
# URL ์ •์˜
if card_type == "space":
url = f"https://huggingface.co/spaces/{item_id}"
elif card_type == "model":
url = f"https://huggingface.co/{item_id}"
else: # dataset
url = f"https://huggingface.co/datasets/{item_id}"
# ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
tags = item.get('tags', [])
pipeline_tag = item.get('pipeline_tag', '')
license = item.get('license', '')
sdk = item.get('sdk', 'N/A')
# AI Rating ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
rating_info = get_rating_info(item, index)
# ์นด๋“œ ํƒ€์ž…๋ณ„ ๊ทธ๋ผ๋ฐ์ด์…˜ ์„ค์ •
if card_type == "space":
gradient_colors = """
rgba(255, 182, 193, 0.7), /* ํŒŒ์Šคํ…” ํ•‘ํฌ */
rgba(173, 216, 230, 0.7), /* ํŒŒ์Šคํ…” ๋ธ”๋ฃจ */
rgba(255, 218, 185, 0.7) /* ํŒŒ์Šคํ…” ํ”ผ์น˜ */
"""
bg_content = f"""
background-image: url(data:image/png;base64,{get_cached_screenshot(url) if get_cached_screenshot(url) else ''});
background-size: cover;
background-position: center;
"""
type_icon = "๐ŸŽฏ"
type_label = "SPACE"
elif card_type == "model":
gradient_colors = """
rgba(110, 142, 251, 0.7), /* ๋ชจ๋ธ ๋ธ”๋ฃจ */
rgba(130, 158, 251, 0.7),
rgba(150, 174, 251, 0.7)
"""
bg_content = f"""
background: linear-gradient(135deg, #6e8efb, #4a6cf7);
padding: 15px;
"""
type_icon = "๐Ÿค–"
type_label = "MODEL"
else: # dataset
gradient_colors = """
rgba(255, 107, 107, 0.7), /* ๋ฐ์ดํ„ฐ์…‹ ๋ ˆ๋“œ */
rgba(255, 127, 127, 0.7),
rgba(255, 147, 147, 0.7)
"""
bg_content = f"""
background: linear-gradient(135deg, #ff6b6b, #ff8787);
padding: 15px;
"""
type_icon = "๐Ÿ“Š"
type_label = "DATASET"
content_bg = f"""
background: linear-gradient(135deg, {gradient_colors});
backdrop-filter: blur(10px);
"""
# ํƒœ๊ทธ ํ‘œ์‹œ (models์™€ datasets์šฉ)
tags_html = ""
if card_type != "space":
tags_html = f"""
<div style='
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-wrap: wrap;
gap: 5px;
justify-content: center;
width: 90%;'>
{' '.join([f'''
<span style='
background: rgba(255,255,255,0.2);
padding: 5px 10px;
border-radius: 15px;
color: white;
font-size: 0.8em;'>
#{tag}
</span>
''' for tag in tags[:5]])}
</div>
"""
# ์นด๋“œ HTML ๋ฐ˜ํ™˜
return f"""
<div class="card" style='
position: relative;
border: none;
padding: 0;
margin: 10px;
border-radius: 20px;
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
background: white;
transition: all 0.3s ease;
overflow: hidden;
min-height: 400px;
cursor: pointer;
transform-origin: center;'
onmouseover="this.style.transform='scale(0.98) translateY(5px)'; this.style.boxShadow='0 5px 15px rgba(0,0,0,0.2)';"
onmouseout="this.style.transform='scale(1) translateY(0)'; this.style.boxShadow='0 10px 20px rgba(0,0,0,0.1)';"
onclick="window.open('{url}', '_blank')">
<!-- ์ƒ๋‹จ ์˜์—ญ -->
<div style='
width: 100%;
height: 200px;
{bg_content}
position: relative;'>
<!-- ์ˆœ์œ„ ๋ฑƒ์ง€ -->
<div style='
position: absolute;
top: 10px;
left: 10px;
background: rgba(0,0,0,0.7);
color: white;
padding: 5px 15px;
border-radius: 20px;
font-weight: bold;
font-size: 0.9em;
backdrop-filter: blur(5px);'>
#{index + 1}
</div>
<!-- ํƒ€์ž… ๋ฑƒ์ง€ -->
<div style='
position: absolute;
top: 10px;
right: 10px;
background: rgba(255,255,255,0.9);
padding: 5px 15px;
border-radius: 20px;
font-weight: bold;
font-size: 0.8em;'>
{type_icon} {type_label}
</div>
{tags_html}
</div>
<!-- ์ฝ˜ํ…์ธ  ์˜์—ญ -->
<div style='
padding: 20px;
{content_bg}
border-radius: 0 0 20px 20px;
border-top: 1px solid rgba(255,255,255,0.5);'>
<h3 style='
margin: 0 0 15px 0;
color: #333;
font-size: 1.3em;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
text-shadow: 1px 1px 1px rgba(255,255,255,0.8);'>
{title}
</h3>
{f'''
<!-- Short Description (Space ์นด๋“œ์—๋งŒ ํ‘œ์‹œ) -->
<div style='
margin: 0 0 15px 0;
color: #444;
font-size: 0.9em;
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
background: rgba(255,255,255,0.4);
padding: 10px;
border-radius: 8px;'>
{short_description}
</div>
''' if card_type == "space" and short_description else ''}
<div style='
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
font-size: 0.9em;
background: rgba(255,255,255,0.3);
padding: 10px;
border-radius: 10px;'>
<div style='color: #444;'>
<span style='margin-right: 5px;'>๐Ÿ‘ค</span> {author}
</div>
<div style='color: #444;'>
<span style='margin-right: 5px;'>โค๏ธ</span> {likes}
</div>
<div style='color: #444; grid-column: span 2;'>
<span style='margin-right: 5px;'>๐Ÿ“…</span> {created}
</div>
</div>
{rating_info}
</div>
</div>
"""
def get_trending_spaces(search_query="", sort_by="rank", progress=gr.Progress()) -> Tuple[str, str]:
"""ํŠธ๋ Œ๋”ฉ ์ŠคํŽ˜์ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ"""
url = "https://huggingface.co/api/spaces"
try:
progress(0, desc="Fetching spaces data...")
params = {
'full': 'true',
'limit': 24
}
response = requests.get(url, params=params)
response.raise_for_status()
spaces = response.json()
# ๊ฒ€์ƒ‰์–ด๋กœ ํ•„ํ„ฐ๋ง
if search_query:
spaces = [space for space in spaces if search_query.lower() in
(space.get('id', '') + ' ' + space.get('title', '')).lower()]
# ์ •๋ ฌ
sort_by = sort_by.lower()
if sort_by == "rising_rate":
spaces.sort(key=lambda x: calculate_rising_rate(x.get('createdAt', ''), 0), reverse=True)
elif sort_by == "popularity":
spaces.sort(key=lambda x: get_popularity_grade(
int(str(x.get('likes', '0')).replace(',', '')),
calculate_rising_rate(x.get('createdAt', ''), 0))[1],
reverse=True)
progress(0.1, desc="Creating gallery...")
html_content = """
<div style='padding: 20px; background: #f5f5f5;'>
<div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'>
"""
for idx, space in enumerate(spaces):
html_content += get_card(space, idx, "space")
progress((0.1 + 0.9 * idx/len(spaces)), desc=f"Loading space {idx+1}/{len(spaces)}...")
html_content += "</div></div>"
progress(1.0, desc="Complete!")
return html_content, f"Found {len(spaces)} spaces"
except Exception as e:
error_html = f'<div style="color: red; padding: 20px;">Error: {str(e)}</div>'
return error_html, f"Error: {str(e)}"
def get_models(search_query="", sort_by="rank", progress=gr.Progress()) -> Tuple[str, str]:
"""์ธ๊ธฐ ๋ชจ๋ธ ๊ฐ€์ ธ์˜ค๊ธฐ"""
url = "https://huggingface.co/api/models"
try:
progress(0, desc="Fetching models data...")
params = {
'full': 'true',
'limit': 300
}
response = requests.get(url, params=params)
response.raise_for_status()
models = response.json()
# ๊ฒ€์ƒ‰์–ด๋กœ ํ•„ํ„ฐ๋ง
if search_query:
models = [model for model in models if search_query.lower() in
(model.get('id', '') + ' ' + model.get('title', '')).lower()]
# ์ •๋ ฌ
sort_by = sort_by.lower()
if sort_by == "rising_rate":
models.sort(key=lambda x: calculate_rising_rate(x.get('createdAt', ''), 0), reverse=True)
elif sort_by == "popularity":
models.sort(key=lambda x: get_popularity_grade(
int(str(x.get('likes', '0')).replace(',', '')),
calculate_rising_rate(x.get('createdAt', ''), 0))[1],
reverse=True)
progress(0.1, desc="Creating gallery...")
html_content = """
<div style='padding: 20px; background: #f5f5f5;'>
<div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'>
"""
for idx, model in enumerate(models):
html_content += get_card(model, idx, "model")
progress((0.1 + 0.9 * idx/len(models)), desc=f"Loading model {idx+1}/{len(models)}...")
html_content += "</div></div>"
progress(1.0, desc="Complete!")
return html_content, f"Found {len(models)} models"
except Exception as e:
error_html = f'<div style="color: red; padding: 20px;">Error: {str(e)}</div>'
return error_html, f"Error: {str(e)}"
def get_datasets(search_query="", sort_by="rank", progress=gr.Progress()) -> Tuple[str, str]:
"""์ธ๊ธฐ ๋ฐ์ดํ„ฐ์…‹ ๊ฐ€์ ธ์˜ค๊ธฐ"""
url = "https://huggingface.co/api/datasets"
try:
progress(0, desc="Fetching datasets data...")
params = {
'full': 'true',
'limit': 300
}
response = requests.get(url, params=params)
response.raise_for_status()
datasets = response.json()
# ๊ฒ€์ƒ‰์–ด๋กœ ํ•„ํ„ฐ๋ง
if search_query:
datasets = [dataset for dataset in datasets if search_query.lower() in
(dataset.get('id', '') + ' ' + dataset.get('title', '')).lower()]
# ์ •๋ ฌ
sort_by = sort_by.lower()
if sort_by == "rising_rate":
datasets.sort(key=lambda x: calculate_rising_rate(x.get('createdAt', ''), 0), reverse=True)
elif sort_by == "popularity":
datasets.sort(key=lambda x: get_popularity_grade(
int(str(x.get('likes', '0')).replace(',', '')),
calculate_rising_rate(x.get('createdAt', ''), 0))[1],
reverse=True)
progress(0.1, desc="Creating gallery...")
html_content = """
<div style='padding: 20px; background: #f5f5f5;'>
<div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'>
"""
for idx, dataset in enumerate(datasets):
html_content += get_card(dataset, idx, "dataset")
progress((0.1 + 0.9 * idx/len(datasets)), desc=f"Loading dataset {idx+1}/{len(datasets)}...")
html_content += "</div></div>"
progress(1.0, desc="Complete!")
return html_content, f"Found {len(datasets)} datasets"
except Exception as e:
error_html = f'<div style="color: red; padding: 20px;">Error: {str(e)}</div>'
return error_html, f"Error: {str(e)}"
# ์ •๋ ฌ ํ•จ์ˆ˜ ์ถ”๊ฐ€
def sort_items(items, sort_by):
if sort_by == "rank":
return items # ์ด๋ฏธ ์ˆœ์œ„๋Œ€๋กœ ์ •๋ ฌ๋˜์–ด ์žˆ์Œ
elif sort_by == "rising_rate":
return sorted(items, key=lambda x: calculate_rising_rate(x.get('createdAt', ''), 0), reverse=True)
elif sort_by == "popularity":
return sorted(items, key=lambda x: get_popularity_grade(int(str(x.get('likes', '0')).replace(',', '')),
calculate_rising_rate(x.get('createdAt', ''), 0))[1], reverse=True)
return items
# API ํ˜ธ์ถœ ํ•จ์ˆ˜ ์ˆ˜์ •
def fetch_items(item_type, search_query="", sort_by="rank", limit=1000):
"""์•„์ดํ…œ ๊ฐ€์ ธ์˜ค๊ธฐ (spaces/models/datasets)"""
base_url = f"https://huggingface.co/api/{item_type}"
params = {
'full': 'true',
'limit': limit,
'search': search_query
}
try:
response = requests.get(base_url, params=params)
response.raise_for_status()
items = response.json()
# ๊ฒ€์ƒ‰์–ด๋กœ ํ•„ํ„ฐ๋ง
if search_query:
items = [item for item in items if search_query.lower() in
(item.get('id', '') + item.get('title', '')).lower()]
# ์ •๋ ฌ
items = sort_items(items, sort_by)
return items[:300] # ์ƒ์œ„ 300๊ฐœ๋งŒ ๋ฐ˜ํ™˜
except Exception as e:
print(f"Error fetching items: {e}")
return []
def get_space_source(space_id: str) -> dict:
"""์ŠคํŽ˜์ด์Šค์˜ ์†Œ์Šค์ฝ”๋“œ ๊ฐ€์ ธ์˜ค๊ธฐ"""
try:
headers = {"Authorization": f"Bearer {os.getenv('HF_TOKEN')}"}
files_to_try = [
'app.py',
'index.html',
'app.js',
'main.py',
'streamlit_app.py',
'gradio_ui.py'
]
source = {}
for file in files_to_try:
url = f"https://huggingface.co/spaces/{space_id}/raw/main/{file}"
response = requests.get(url, headers=headers)
if response.status_code == 200:
source[file] = response.text
return source if source else {"app.py": "", "index.html": ""}
except Exception as e:
print(f"Error fetching source for {space_id}: {str(e)}")
return {"app.py": "", "index.html": ""}
def analyze_spaces(progress=gr.Progress()):
"""์ŠคํŽ˜์ด์Šค ๋ถ„์„ ๋ฐ HTML ์ƒ์„ฑ"""
try:
url = "https://huggingface.co/api/spaces"
response = requests.get(url, params={'full': 'true', 'limit': 24})
response.raise_for_status()
spaces = response.json()[:24]
html_content = "<div style='padding: 20px;'>"
for idx, space in enumerate(spaces):
progress((idx + 1) / 24, desc=f"๋ถ„์„ ์ค‘... {idx+1}/24")
try:
# ์Šคํฌ๋ฆฐ์ƒท ์ฒ˜๋ฆฌ
space_url = f"https://huggingface.co/spaces/{space['id']}"
screenshot_base64 = get_cached_screenshot(space_url)
# ํ…์ŠคํŠธ ๋ถ„์„
project_name = space['id'].split('/')[-1]
source = get_space_source(space['id'])
source_code = source["app.py"] or source["index.html"]
prompt = f"""
๋‹ค์Œ ์ŠคํŽ˜์ด์Šค๋ฅผ ๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”:
์ŠคํŽ˜์ด์Šค ์ด๋ฆ„: {project_name}
์ˆœ์œ„: {idx + 1}์œ„
๋‹ค์Œ ํ˜•์‹์œผ๋กœ ์ž‘์„ฑ:
1. ์ˆœ์œ„์™€ ์ด๋ฆ„ ์†Œ๊ฐœ
2. ์ฃผ์š” ๊ธฐ๋Šฅ ์„ค๋ช…
3. ํŠน์ง•์ ์ธ ์žฅ์ 
"""
messages = [
{"role": "system", "content": "๊ฐ„๋‹จ๋ช…๋ฃŒํ•œ ์„ค๋ช…์„ ์ œ๊ณตํ•˜๋Š” ๋ฆฌํฌํ„ฐ์ž…๋‹ˆ๋‹ค."},
{"role": "user", "content": prompt}
]
response = hf_client.chat_completion(
messages,
max_tokens=150,
temperature=0.7
)
analysis = response.choices[0].message.content.strip()
# HTML ์นด๋“œ ์ƒ์„ฑ
html_content += f"""
<div style='
background: white;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);'>
<div style='display: flex; gap: 20px;'>
<div style='flex: 2;'>
<h3>Space #{idx + 1}</h3>
<textarea style='
width: 100%;
min-height: 100px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
margin-top: 10px;'
>{analysis}</textarea>
</div>
<div style='flex: 1;'>
{f'<img src="data:image/jpeg;base64,{screenshot_base64}" style="width: 100%; border-radius: 5px;">' if screenshot_base64 else ''}
</div>
</div>
</div>
"""
except Exception as e:
print(f"Error processing space {space['id']}: {e}")
html_content += f"""
<div style='
background: white;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);'>
<h3>Space #{idx + 1}</h3>
<p>๋ถ„์„์„ ์ค€๋น„์ค‘์ž…๋‹ˆ๋‹ค.</p>
</div>
"""
html_content += "</div>"
return html_content
except Exception as e:
print(f"Analysis error: {e}")
return "<div style='color: red; padding: 20px;'>๋ถ„์„ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.</div>"
def analyze_top_spaces(progress=gr.Progress()) -> Tuple[str, str]:
"""์ƒ์œ„ 24๊ฐœ ์ŠคํŽ˜์ด์Šค ๋ถ„์„"""
try:
progress(0, desc="์ŠคํŽ˜์ด์Šค ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๋Š” ์ค‘...")
url = "https://huggingface.co/api/spaces"
response = requests.get(url, params={'full': 'true', 'limit': 24})
response.raise_for_status()
spaces = response.json()[:24]
# ์ƒ๋‹จ ์ž…๋ ฅ ๋ฐ•์Šค์™€ ๊ธฐ๋ณธ ํ…์ŠคํŠธ๋ฅผ ํฌํ•จํ•œ HTML ์‹œ์ž‘
html_content = """
<div style='padding: 20px; background: #ffffff;'>
<div style='margin-bottom: 30px;'>
<textarea id='intro_text' rows='4' style='
width: 100%;
padding: 15px;
border: 1px solid #ddd;
border-radius: 10px;
font-size: 1.1em;
line-height: 1.5;
resize: vertical;
background: #f8f9fa;
'>์•ˆ๋…•ํ•˜์„ธ์š”. ๋งค์ผ ๊ธ€๋กœ๋ฒŒ ์ตœ์‹  AI ์ธ๊ธฐ ํŠธ๋ Œ๋“œ ์„œ๋น„์Šค๋ฅผ ์•Œ์•„๋ณด๋Š” '๋ฐ์ผ๋ฆฌ AI ํŠธ๋ Œ๋”ฉ' ๋‰ด์Šค์ž…๋‹ˆ๋‹ค. ์˜ค๋Š˜์˜ ํ—ˆ๊น…ํŽ˜์ด์Šค ์ธ๊ธฐ ์ˆœ์œ„ 1์œ„๋ถ€ํ„ฐ 24์œ„๊นŒ์ง€, ๋ถ„์„๊ณผ ํ•ต์‹ฌ ๋‚ด์šฉ์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.</textarea>
</div>
<style>
.script-card {
background: white !important;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
border: 1px solid #e0e0e0;
}
.script-content {
color: #444 !important;
font-size: 1.1em;
line-height: 1.6;
white-space: pre-line;
}
</style>
"""
for idx, space in enumerate(spaces):
progress((idx + 1) / 24, desc=f"๋ถ„์„ ์ค‘... {idx+1}/24")
try:
source = get_space_source(space['id'])
source_code = source["app.py"] or source["index.html"]
# ์ŠคํŽ˜์ด์Šค ID์—์„œ ์‚ฌ์šฉ์ž๋ช… ์ œ๊ฑฐํ•˜๊ณ  ํ”„๋กœ์ ํŠธ๋ช…๋งŒ ์ถ”์ถœ
project_name = space['id'].split('/')[-1]
prompt = f"""
๋‹ค์Œ HuggingFace ์ŠคํŽ˜์ด์Šค๋ฅผ ์œ ํŠœ๋ธŒ ๋‰ด์Šค ๋ฆฌํฌํŠธ ํ˜•์‹์œผ๋กœ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.
์‹œ์ž‘์€ ๋ฐ˜๋“œ์‹œ "์˜ค๋Š˜์˜ ์ธ๊ธฐ์ˆœ์œ„ {idx + 1}์œ„์ธ {project_name}์ž…๋‹ˆ๋‹ค."๋กœ ์‹œ์ž‘ํ•˜๊ณ ,
์ด์–ด์„œ ์ฃผ์š” ๊ธฐ๋Šฅ, ํŠน์ง•, ํ™œ์šฉ๋ฐฉ์•ˆ์„ 2-3๋ฌธ์žฅ์œผ๋กœ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.
์ „์ฒด ๊ธธ์ด๋Š” 3-4๋ฌธ์žฅ์œผ๋กœ ์ œํ•œํ•˜๊ณ , ์„ค๋ช…์€ ๋‰ด์Šค ๋ฆฌํฌํ„ฐ์ฒ˜๋Ÿผ ๋ช…ํ™•ํ•˜๊ณ  ์ „๋ฌธ์ ์œผ๋กœ ํ•ด์ฃผ์„ธ์š”.
์†Œ์Šค์ฝ”๋“œ:
```
{source_code[:1500]}
```
"""
messages = [
{"role": "system", "content": "AI ๊ธฐ์ˆ  ์ „๋ฌธ ๋‰ด์Šค ๋ฆฌํฌํ„ฐ์ž…๋‹ˆ๋‹ค."},
{"role": "user", "content": prompt}
]
response = hf_client.chat_completion(
messages,
max_tokens=2000,
temperature=0.7
)
script = response.choices[0].message.content.strip()
html_content += f"""
<div class='script-card'>
<div class='script-content'>{script}</div>
</div>
"""
except Exception as e:
print(f"Error analyzing space {space['id']}: {e}")
html_content += f"""
<div class='script-card'>
<div class='script-content' style='color: red !important;'>
์ˆœ์œ„ {idx + 1}์œ„ ๋ถ„์„ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.
</div>
</div>
"""
html_content += "</div>"
return html_content, f"24๊ฐœ ์ŠคํŽ˜์ด์Šค ๋ถ„์„ ์™„๋ฃŒ"
except Exception as e:
error_msg = f"Error: {str(e)}"
return f"<div style='color: red; padding: 20px;'>{error_msg}</div>", error_msg
def analyze_single_space(space: dict, source_code: str) -> str:
"""๋‹จ์ผ ์ŠคํŽ˜์ด์Šค ๋ถ„์„"""
try:
if not source_code:
return "์†Œ์Šค์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
prompt = f"""
๋‹ค์Œ ์ŠคํŽ˜์ด์Šค์˜ ์†Œ์Šค์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”:
```
{source_code[:4000]}
```
๋‹ค์Œ ํ•ญ๋ชฉ์„ ๊ฐ๊ฐ ํ•œ ์ค„๋กœ ์š”์•ฝํ•ด์ฃผ์„ธ์š”:
1. ๊ฐœ์š”:
2. ์š”์•ฝ:
3. ํŠน์ง• ๋ฐ ์žฅ์ :
4. ์‚ฌ์šฉ ๋Œ€์ƒ:
5. ์‚ฌ์šฉ ๋ฐฉ๋ฒ•:
6. ์œ ์‚ฌ ์„œ๋น„์Šค์™€์˜ ์ฐจ๋ณ„์ :
"""
messages = [
{"role": "system", "content": "์†Œ์Šค์ฝ”๋“œ ๋ถ„์„ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค."},
{"role": "user", "content": prompt}
]
response = hf_client.chat_completion(
messages,
max_tokens=3900,
temperature=0.3
)
return response.choices[0].message.content
except Exception as e:
return f"๋ถ„์„ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
def create_editable_space_analysis(progress=gr.Progress()) -> List[str]:
"""24๊ฐœ ์ŠคํŽ˜์ด์Šค ๋ถ„์„ ํ…์ŠคํŠธ ์ƒ์„ฑ"""
try:
progress(0, desc="์ŠคํŽ˜์ด์Šค ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๋Š” ์ค‘...")
url = "https://huggingface.co/api/spaces"
response = requests.get(url, params={'full': 'true', 'limit': 24})
response.raise_for_status()
spaces = response.json()[:24]
analysis_texts = []
for idx, space in enumerate(spaces):
progress((idx + 1) / 24, desc=f"๋ถ„์„ ์ค‘... {idx+1}/24")
try:
source = get_space_source(space['id'])
source_code = source["app.py"] or source["index.html"]
# ํ”„๋กœ์ ํŠธ๋ช…๋งŒ ์ถ”์ถœ
project_name = space['id'].split('/')[-1]
prompt = f"""
๋‹ค์Œ HuggingFace ์ŠคํŽ˜์ด์Šค๋ฅผ ๋ถ„์„ํ•˜์—ฌ ๋‰ด์Šค ๋ฆฌํฌํŠธ ํ˜•์‹์œผ๋กœ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”:
์‹œ์ž‘์€ ๋ฐ˜๋“œ์‹œ "์˜ค๋Š˜์˜ ์ธ๊ธฐ์ˆœ์œ„ {idx + 1}์œ„์ธ {project_name}์ž…๋‹ˆ๋‹ค."๋กœ ์‹œ์ž‘ํ•˜๊ณ ,
์ด์–ด์„œ ์ฃผ์š” ๊ธฐ๋Šฅ, ํŠน์ง•, ํ™œ์šฉ๋ฐฉ์•ˆ์„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.
์†Œ์Šค์ฝ”๋“œ:
```
{source_code[:1500]}
```
"""
messages = [
{"role": "system", "content": "AI ๊ธฐ์ˆ  ์ „๋ฌธ ๋‰ด์Šค ๋ฆฌํฌํ„ฐ์ž…๋‹ˆ๋‹ค."},
{"role": "user", "content": prompt}
]
response = hf_client.chat_completion(
messages,
max_tokens=2000,
temperature=0.7
)
analysis_texts.append(response.choices[0].message.content.strip())
except Exception as e:
analysis_texts.append(f"์˜ค๋Š˜์˜ ์ธ๊ธฐ์ˆœ์œ„ {idx + 1}์œ„์ธ {project_name}์ž…๋‹ˆ๋‹ค.")
return analysis_texts
except Exception as e:
return [f"์ˆœ์œ„ {i+1}์œ„ ๋ถ„์„์„ ์ค€๋น„์ค‘์ž…๋‹ˆ๋‹ค." for i in range(24)]
def generate_video(intro_text, analysis_html):
"""๋น„๋””์˜ค ์ƒ์„ฑ"""
try:
# HTML์—์„œ ํ…์ŠคํŠธ ์ถ”์ถœ
soup = BeautifulSoup(analysis_html, 'html.parser')
texts = [intro_text] + [p.text for p in soup.find_all('p')]
temp_dir = tempfile.mkdtemp()
clips = []
# ๊ฐ ํ…์ŠคํŠธ์— ๋Œ€ํ•œ ํด๋ฆฝ ์ƒ์„ฑ
for idx, text in enumerate(texts):
if not text or len(text.strip()) == 0:
continue
# ์ด๋ฏธ์ง€ ์ƒ์„ฑ
img = Image.new('RGB', (800, 600), (0, 0, 0))
draw = ImageDraw.Draw(img)
# ํ…์ŠคํŠธ๋ฅผ ์—ฌ๋Ÿฌ ์ค„๋กœ ๋‚˜๋ˆ„์–ด ๊ทธ๋ฆฌ๊ธฐ
lines = textwrap.wrap(text, width=40)
y = 40
for line in lines:
draw.text((40, y), line, fill=(255, 255, 255))
y += 30
# ์Œ์„ฑ ์ƒ์„ฑ
tts = gTTS(text=text, lang='ko', slow=False)
audio_path = os.path.join(temp_dir, f"audio_{idx}.mp3")
tts.save(audio_path)
audio_clip = AudioFileClip(audio_path)
# ํด๋ฆฝ ์ƒ์„ฑ
video_clip = ImageClip(np.array(img))
video_clip = video_clip.set_duration(audio_clip.duration)
video_clip = video_clip.set_audio(audio_clip)
clips.append(video_clip)
# ์ตœ์ข… ์˜์ƒ ์ƒ์„ฑ
final_clip = concatenate_videoclips(clips)
output_path = "output_video.mp4"
final_clip.write_videofile(
output_path,
fps=24,
codec='libx264',
audio_codec='aac'
)
return output_path
except Exception as e:
print(f"Video generation error: {e}")
traceback.print_exc()
return None
def create_interface():
with gr.Blocks(title="HuggingFace Trending Board", css="""
.search-sort-container {
background: linear-gradient(135deg, rgba(255,255,255,0.95), rgba(240,240,255,0.95));
border-radius: 15px;
padding: 20px;
margin: 10px 0;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
overflow: visible;
}
.search-box {
border: 2px solid #e1e1e1;
border-radius: 10px;
padding: 12px;
transition: all 0.3s ease;
background: linear-gradient(135deg, #ffffff, #f8f9ff);
width: 100%;
}
.search-box:focus {
border-color: #7b61ff;
box-shadow: 0 0 0 2px rgba(123,97,255,0.2);
background: linear-gradient(135deg, #ffffff, #f0f3ff);
}
.refresh-btn {
background: linear-gradient(135deg, #7b61ff, #6366f1);
color: white;
border: none;
padding: 10px 20px;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
width: 120px;
height: 80px !important;
display: flex;
align-items: center;
justify-content: center;
margin-left: auto;
font-size: 1.2em !important;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.refresh-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0,0,0,0.2);
background: linear-gradient(135deg, #8b71ff, #7376f1);
}
""") as interface:
gr.Markdown("""
# ๐Ÿค— HuggingFace Trending 24 NEWS
<div style='margin-bottom: 20px; padding: 10px; background: linear-gradient(135deg, rgba(123,97,255,0.1), rgba(99,102,241,0.1)); border-radius: 10px;'>
HuggingFace Trending Spaces Top 24 NEWS
</div>
""")
with gr.Tabs() as tabs:
# Spaces ํƒญ
with gr.Tab("๐ŸŽฏ Trending Spaces"):
with gr.Row(elem_classes="search-sort-container"):
with gr.Column(scale=2):
spaces_search = gr.Textbox(
label="๐Ÿ” Search Spaces",
placeholder="Enter keywords to search...",
elem_classes="search-box"
)
with gr.Column(scale=2):
spaces_sort = gr.Radio(
choices=["rank", "rising_rate", "popularity"],
value="rank",
label="Sort by",
interactive=True
)
with gr.Column(scale=1):
spaces_refresh_btn = gr.Button(
"๐Ÿ”„ Refresh",
variant="primary",
elem_classes="refresh-btn"
)
spaces_gallery = gr.HTML()
spaces_status = gr.Markdown("Loading...")
# Models ํƒญ
with gr.Tab("๐Ÿค– Trending Models"):
with gr.Row(elem_classes="search-sort-container"):
with gr.Column(scale=2):
models_search = gr.Textbox(
label="๐Ÿ” Search Models",
placeholder="Enter keywords to search...",
elem_classes="search-box"
)
with gr.Column(scale=2):
models_sort = gr.Radio(
choices=["rank", "rising_rate", "popularity"],
value="rank",
label="Sort by",
interactive=True
)
with gr.Column(scale=1):
models_refresh_btn = gr.Button(
"๐Ÿ”„ Refresh",
variant="primary",
elem_classes="refresh-btn"
)
models_gallery = gr.HTML()
models_status = gr.Markdown("Loading...")
# Datasets ํƒญ
with gr.Tab("๐Ÿ“Š Trending Datasets"):
with gr.Row(elem_classes="search-sort-container"):
with gr.Column(scale=2):
datasets_search = gr.Textbox(
label="๐Ÿ” Search Datasets",
placeholder="Enter keywords to search...",
elem_classes="search-box"
)
with gr.Column(scale=2):
datasets_sort = gr.Radio(
choices=["rank", "rising_rate", "popularity"],
value="rank",
label="Sort by",
interactive=True
)
with gr.Column(scale=1):
datasets_refresh_btn = gr.Button(
"๐Ÿ”„ Refresh",
variant="primary",
elem_classes="refresh-btn"
)
datasets_gallery = gr.HTML()
datasets_status = gr.Markdown("Loading...")
with gr.Tab("๐Ÿ” Top 24 Spaces Analysis"):
with gr.Row():
analysis_refresh_btn = gr.Button(
"๐Ÿ”„ Analyze All 24 Spaces",
variant="primary"
)
# ์ธํŠธ๋กœ ์„น์…˜
with gr.Row():
with gr.Column(scale=3):
intro_text = gr.Textbox(
value="์•ˆ๋…•ํ•˜์„ธ์š”. ๋งค์ผ ๊ธ€๋กœ๋ฒŒ ์ตœ์‹  AI ์ธ๊ธฐ ํŠธ๋ Œ๋“œ ์„œ๋น„์Šค๋ฅผ ์•Œ์•„๋ณด๋Š” '๋ฐ์ผ๋ฆฌ AI ํŠธ๋ Œ๋”ฉ' ๋‰ด์Šค์ž…๋‹ˆ๋‹ค.",
label="์ธํŠธ๋กœ ํ…์ŠคํŠธ",
lines=4
)
# ๋ถ„์„ ๊ฒฐ๊ณผ ์ปจํ…Œ์ด๋„ˆ๋“ค
analysis_boxes = []
space_images = []
# Analysis ํƒญ์—์„œ ์ด๋ฏธ์ง€ ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ ๋ถ€๋ถ„ ์ˆ˜์ •
for i in range(24):
with gr.Row():
with gr.Column(scale=3):
text_box = gr.Textbox(
label=f"Space #{i+1}",
lines=3,
interactive=True
)
analysis_boxes.append(text_box)
with gr.Column(scale=1):
img = gr.Image(
label=f"Screenshot #{i+1}",
type="filepath",
interactive=True, # ์ด๋ฏธ์ง€ ๊ต์ฒด ๊ฐ€๋Šฅํ•˜๋„๋ก ์„ค์ •
height=200, # ์ด๋ฏธ์ง€ ๋†’์ด ์„ค์ •
sources=["upload", "clipboard"] # ์—…๋กœ๋“œ์™€ ํด๋ฆฝ๋ณด๋“œ ๋ถ™์—ฌ๋„ฃ๊ธฐ ํ—ˆ์šฉ
)
space_images.append(img)
# ๋น„๋””์˜ค ์ƒ์„ฑ ์„น์…˜
with gr.Row():
generate_btn = gr.Button(
"๐ŸŽฌ Generate Video",
variant="primary"
)
video_output = gr.Video(label="Generated Video")
# ์ด๋ฏธ์ง€ ์—…๋ฐ์ดํŠธ ๋ฒ„ํŠผ
with gr.Row():
update_images_btn = gr.Button(
"๐Ÿ”„ Update Screenshots",
variant="secondary"
)
# ์ด๋ฏธ์ง€ ๊ต์ฒด ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์ถ”๊ฐ€
for img in space_images:
img.change(
fn=lambda x: x, # ๊ฐ„๋‹จํ•œ ํŒจ์Šค์Šค๋ฃจ ํ•จ์ˆ˜
inputs=[img],
outputs=[img]
)
# Event handlers
spaces_refresh_btn.click(
fn=get_trending_spaces,
inputs=[spaces_search, spaces_sort],
outputs=[spaces_gallery, spaces_status]
)
models_refresh_btn.click(
fn=get_models,
inputs=[models_search, models_sort],
outputs=[models_gallery, models_status]
)
datasets_refresh_btn.click(
fn=get_datasets,
inputs=[datasets_search, datasets_sort],
outputs=[datasets_gallery, datasets_status]
)
# Analysis ํƒญ์˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋“ค
analysis_refresh_btn.click(
fn=on_analyze,
outputs=analysis_boxes + space_images
)
generate_btn.click(
fn=on_generate_video,
inputs=[intro_text] + analysis_boxes,
outputs=video_output
)
update_images_btn.click(
fn=update_screenshots,
outputs=space_images
)
# ๊ฒ€์ƒ‰์–ด ๋ณ€๊ฒฝ ์‹œ ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ
spaces_search.change(
fn=get_trending_spaces,
inputs=[spaces_search, spaces_sort],
outputs=[spaces_gallery, spaces_status]
)
models_search.change(
fn=get_models,
inputs=[models_search, models_sort],
outputs=[models_gallery, models_status]
)
datasets_search.change(
fn=get_datasets,
inputs=[datasets_search, datasets_sort],
outputs=[datasets_gallery, datasets_status]
)
# ์ •๋ ฌ ๋ฐฉ์‹ ๋ณ€๊ฒฝ ์‹œ ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ
spaces_sort.change(
fn=get_trending_spaces,
inputs=[spaces_search, spaces_sort],
outputs=[spaces_gallery, spaces_status]
)
models_sort.change(
fn=get_models,
inputs=[models_search, models_sort],
outputs=[models_gallery, models_status]
)
datasets_sort.change(
fn=get_datasets,
inputs=[datasets_search, datasets_sort],
outputs=[datasets_gallery, datasets_status]
)
# ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ
interface.load(
fn=get_trending_spaces,
inputs=[spaces_search, spaces_sort],
outputs=[spaces_gallery, spaces_status]
)
interface.load(
fn=get_models,
inputs=[models_search, models_sort],
outputs=[models_gallery, models_status]
)
interface.load(
fn=get_datasets,
inputs=[datasets_search, datasets_sort],
outputs=[datasets_gallery, datasets_status]
)
return interface
def on_analyze(progress=gr.Progress()):
"""๋ถ„์„ ์‹คํ–‰ ๋ฐ ํ…์ŠคํŠธ๋ฐ•์Šค/์ด๋ฏธ์ง€ ์—…๋ฐ์ดํŠธ"""
try:
url = "https://huggingface.co/api/spaces"
response = requests.get(url, params={'full': 'true', 'limit': 24})
response.raise_for_status()
spaces = response.json()[:24]
text_results = []
image_results = []
temp_dir = Path("temp_screenshots")
temp_dir.mkdir(exist_ok=True)
for idx, space in enumerate(spaces):
progress((idx + 1) / 24, desc=f"๋ถ„์„ ์ค‘... {idx+1}/24")
try:
# ์Šคํฌ๋ฆฐ์ƒท ์ฒ˜๋ฆฌ
space_url = f"https://huggingface.co/spaces/{space['id']}"
screenshot_path = temp_dir / f"space_{idx:03d}.jpg"
# ์ด๋ฏธ์ง€ ์ €์žฅ
screenshot_base64 = get_cached_screenshot(space_url)
if screenshot_base64:
try:
img_data = base64.b64decode(screenshot_base64)
with open(screenshot_path, 'wb') as f:
f.write(img_data)
image_results.append(str(screenshot_path))
except Exception as e:
print(f"Screenshot save error: {e}")
image_results.append(None)
else:
image_results.append(None)
# ์†Œ์Šค์ฝ”๋“œ ๊ฐ€์ ธ์˜ค๊ธฐ
source = get_space_source(space['id'])
source_code = ""
# ์†Œ์Šค์ฝ”๋“œ ์šฐ์„ ์ˆœ์œ„ ์„ค์ •
if source.get("app.py"):
source_code = source["app.py"]
elif source.get("streamlit_app.py"):
source_code = source["streamlit_app.py"]
elif source.get("gradio_ui.py"):
source_code = source["gradio_ui.py"]
elif source.get("main.py"):
source_code = source["main.py"]
elif source.get("index.html"):
source_code = source["index.html"]
if not source_code.strip():
text_results.append(f"์˜ค๋Š˜์˜ ์ธ๊ธฐ์ˆœ์œ„ {idx + 1}์œ„ ์ŠคํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค. ์†Œ์Šค์ฝ”๋“œ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์—†์–ด ์ž์„ธํ•œ ๋ถ„์„์€ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.")
continue
# ํ…์ŠคํŠธ ๋ถ„์„
project_name = space['id'].split('/')[-1]
# ์†Œ์Šค์ฝ”๋“œ ๋ถ„์„์„ ์œ„ํ•œ ํ”„๋กฌํ”„ํŠธ
prompt = f"""
๋‹ค์Œ HuggingFace ์ŠคํŽ˜์ด์Šค์˜ ์†Œ์Šค์ฝ”๋“œ๋ฅผ ์ž์„ธํžˆ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”:
์ŠคํŽ˜์ด์Šค ์ •๋ณด:
- ์ด๋ฆ„: {project_name}
- ์ˆœ์œ„: {idx + 1}์œ„
- URL: {space_url}
์†Œ์Šค์ฝ”๋“œ:
```python
{source_code[:2000]}
```
๋‹ค์Œ ๋‚ด์šฉ์„ ๋ถ„์„ํ•˜์—ฌ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”:
1. ์ด ์ŠคํŽ˜์ด์Šค๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ์ฃผ์š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ํ”„๋ ˆ์ž„์›Œํฌ
2. ๊ตฌํ˜„๋œ ํ•ต์‹ฌ ๊ธฐ๋Šฅ๊ณผ ์ž‘๋™ ๋ฐฉ์‹
3. ๊ธฐ์ˆ ์  ํŠน์ง•๊ณผ ์žฅ์ 
์‘๋‹ต ํ˜•์‹:
- ๋‰ด์Šค ๋ฆฌํฌํ„ฐ์ฒ˜๋Ÿผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์„ค๋ช…
- ๊ธฐ์ˆ ์  ๋‚ด์šฉ์„ ํฌํ•จํ•˜๋˜ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ฒŒ ์„ค๋ช…
- ์‹ค์ œ ์†Œ์Šค์ฝ”๋“œ์—์„œ ํ™•์ธ๋œ ๋‚ด์šฉ๋งŒ ํฌํ•จ
-'์ธ๋…•ํ•˜์„ธ์š”'์™€ ๊ฐ™์€ ์ธ์‚ฌ์™€ '์ž๊ธฐ์†Œ๊ฐœ'๋Š” ํฌํ•จํ•˜์ง€๋ง์•„๋ผ.
- ์ตœ๋Œ€ 7๋ฌธ์žฅ ์ด๋‚ด๋กœ ๊ฐ„๋‹จ๋ช…๋ฃŒํ•˜๊ฒŒ ์ž‘์„ฑ(๋ฐ˜๋“œ์‹œ ์™„์„ฑ๋œ ๋ฌธ์žฅ์ด ์•„๋‹Œ๊ฒฝ์šฐ ์ถœ๋ ฅํ•˜์ง€๋ง๊ฒƒ)
"""
messages = [
{
"role": "system",
"content": "์ €๋Š” ์†Œ์Šค์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•˜์—ฌ ๊ธฐ์ˆ ์  ๋‚ด์šฉ์„ 10์„ธ ์•„๋™๋„ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•˜๋Š” AI ๊ธฐ์ˆ  ์ „๋ฌธ ๋ฆฌํฌํ„ฐ์ž…๋‹ˆ๋‹ค."
},
{"role": "user", "content": prompt}
]
response = hf_client.chat_completion(
messages,
max_tokens=1000,
temperature=0.3
)
analysis = response.choices[0].message.content.strip()
# ๋ถ„์„ ๊ฒฐ๊ณผ๊ฐ€ ์‹ค์ œ ์†Œ์Šค์ฝ”๋“œ ๊ธฐ๋ฐ˜์ธ์ง€ ํ™•์ธ
if "๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ" in analysis or "ํ”„๋ ˆ์ž„์›Œํฌ" in analysis or "ํ•จ์ˆ˜" in analysis:
text_results.append(analysis)
else:
# ์žฌ์‹œ๋„
prompt += "\n\n์ฃผ์˜: ๋ฐ˜๋“œ์‹œ ์†Œ์Šค์ฝ”๋“œ์—์„œ ํ™•์ธ๋œ ์‹ค์ œ ๊ธฐ์ˆ ์  ๋‚ด์šฉ๋งŒ ํฌํ•จํ•˜์—ฌ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”."
messages[1]["content"] = prompt
response = hf_client.chat_completion(
messages,
max_tokens=300,
temperature=0.2
)
text_results.append(response.choices[0].message.content.strip())
except Exception as e:
print(f"Error processing space {space['id']}: {e}")
text_results.append(f"ํ˜„์žฌ {idx + 1}์œ„ ์ŠคํŽ˜์ด์Šค์— ๋Œ€ํ•œ ๋ถ„์„์„ ์ค€๋น„์ค‘์ž…๋‹ˆ๋‹ค.")
if len(image_results) <= idx:
image_results.append(None)
# ๊ฒฐ๊ณผ ๊ฐœ์ˆ˜ ๋งž์ถ”๊ธฐ
while len(text_results) < 24:
text_results.append("๋ถ„์„์„ ์ค€๋น„์ค‘์ž…๋‹ˆ๋‹ค.")
while len(image_results) < 24:
image_results.append(None)
return text_results + image_results
except Exception as e:
print(f"Analysis error: {e}")
return ["๋ถ„์„์„ ์ค€๋น„์ค‘์ž…๋‹ˆ๋‹ค."] * 24 + [None] * 24
def on_generate_video(intro_text, *texts):
"""์˜์ƒ ์ƒ์„ฑ"""
try:
temp_dir = tempfile.mkdtemp()
clips = []
# ์ธํŠธ๋กœ ์ฒ˜๋ฆฌ - intro.png ์‚ฌ์šฉ
try:
intro_img = Image.open("intro.png")
# ํฌ๊ธฐ๊ฐ€ ๋‹ค๋ฅธ ๊ฒฝ์šฐ ๋ฆฌ์‚ฌ์ด์ฆˆ
if intro_img.size != (800, 600):
intro_img = intro_img.resize((800, 600), Image.Resampling.LANCZOS)
except Exception as e:
print(f"Error loading intro image: {e}")
# intro.png๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ ๊ฒ€์€ ๋ฐฐ๊ฒฝ์— ํ…์ŠคํŠธ
intro_img = Image.new('RGB', (800, 600), (0, 0, 0))
draw = ImageDraw.Draw(intro_img)
try:
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20)
except:
font = ImageFont.load_default()
lines = textwrap.wrap(intro_text, width=40)
y = 40
for line in lines:
draw.text((40, y), line, fill=(255, 255, 255), font=font)
y += 30
# ์ธํŠธ๋กœ ํด๋ฆฝ ์ƒ์„ฑ
intro_audio = gTTS(text=intro_text, lang='ko', slow=False)
intro_audio_path = os.path.join(temp_dir, "intro.mp3")
intro_audio.save(intro_audio_path)
intro_audio_clip = AudioFileClip(intro_audio_path)
intro_clip = ImageClip(np.array(intro_img))
intro_clip = intro_clip.set_duration(intro_audio_clip.duration)
intro_clip = intro_clip.set_audio(intro_audio_clip)
clips.append(intro_clip)
# ๊ฐ ์ŠคํŽ˜์ด์Šค ์ฒ˜๋ฆฌ
for idx, text in enumerate(texts):
if not text or len(str(text).strip()) == 0:
continue
# ์Šคํฌ๋ฆฐ์ƒท ์ด๋ฏธ์ง€ ๊ฐ€์ ธ์˜ค๊ธฐ
screenshot_path = f"temp_screenshots/space_{idx:03d}.jpg"
if os.path.exists(screenshot_path):
img = Image.open(screenshot_path)
# ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์กฐ์ •
img = img.resize((800, 600), Image.Resampling.LANCZOS)
else:
# ์Šคํฌ๋ฆฐ์ƒท์ด ์—†๋Š” ๊ฒฝ์šฐ ๊ฒ€์€ ๋ฐฐ๊ฒฝ
img = Image.new('RGB', (800, 600), (0, 0, 0))
# ์Œ์„ฑ ์ƒ์„ฑ
tts = gTTS(text=str(text), lang='ko', slow=False)
audio_path = os.path.join(temp_dir, f"audio_{idx}.mp3")
tts.save(audio_path)
audio_clip = AudioFileClip(audio_path)
# ํด๋ฆฝ ์ƒ์„ฑ (์ด๋ฏธ์ง€ ์‚ฌ์šฉ)
video_clip = ImageClip(np.array(img))
video_clip = video_clip.set_duration(audio_clip.duration)
video_clip = video_clip.set_audio(audio_clip)
clips.append(video_clip)
# ์ตœ์ข… ์˜์ƒ ์ƒ์„ฑ
final_clip = concatenate_videoclips(clips)
output_path = "output_video.mp4"
final_clip.write_videofile(
output_path,
fps=24,
codec='libx264',
audio_codec='aac'
)
return output_path
except Exception as e:
print(f"Video generation error: {e}")
traceback.print_exc()
return None
finally:
try:
if 'temp_dir' in locals():
shutil.rmtree(temp_dir)
except Exception as e:
print(f"Cleanup error: {e}")
def update_screenshots():
"""์Šคํฌ๋ฆฐ์ƒท ์ผ๊ด„ ์—…๋ฐ์ดํŠธ"""
try:
url = "https://huggingface.co/api/spaces"
response = requests.get(url, params={'full': 'true', 'limit': 24})
spaces = response.json()[:24]
image_paths = []
temp_dir = Path("temp_screenshots")
temp_dir.mkdir(exist_ok=True)
for idx, space in enumerate(spaces):
try:
space_url = f"https://huggingface.co/spaces/{space['id']}"
time.sleep(5) # ์ถฉ๋ถ„ํ•œ ๋กœ๋”ฉ ์‹œ๊ฐ„
screenshot_base64 = get_cached_screenshot(space_url)
screenshot_path = temp_dir / f"space_{idx:03d}.jpg"
if screenshot_base64:
try:
img_data = base64.b64decode(screenshot_base64)
# ์ด๋ฏธ์ง€ ์ €์žฅ ๋ฐ ์ตœ์ ํ™”
with open(screenshot_path, 'wb') as f:
f.write(img_data)
# ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์ตœ์ ํ™”
with Image.open(screenshot_path) as img:
img = img.resize((800, 600), Image.Resampling.LANCZOS)
img.save(screenshot_path, format="JPEG", quality=85, optimize=True)
image_paths.append(str(screenshot_path))
except Exception as e:
print(f"Screenshot save error: {e}")
image_paths.append(None)
else:
image_paths.append(None)
except Exception as e:
print(f"Error capturing screenshot for space {idx+1}: {e}")
image_paths.append(None)
return image_paths
except Exception as e:
print(f"Update screenshots error: {e}")
return [None] * 24
if __name__ == "__main__":
try:
CACHE_DIR.mkdir(exist_ok=True)
cleanup_cache()
demo = create_interface()
demo.launch(
share=True,
inbrowser=True,
show_api=False,
max_threads=4
)
except Exception as e:
print(f"Application error: {e}")