|
|
import streamlit as st |
|
|
import requests |
|
|
import os |
|
|
from moviepy.editor import VideoFileClip, concatenate_videoclips |
|
|
import tempfile |
|
|
import shutil |
|
|
|
|
|
|
|
|
PIXABAY_API_KEY = os.getenv("PIXABAY_API_KEY") |
|
|
|
|
|
if not PIXABAY_API_KEY: |
|
|
st.error("لطفاً PIXABAY_API_KEY را در Settings → Secrets فضای Hugging Face قرار دهید!") |
|
|
st.stop() |
|
|
|
|
|
VIDEO_SEARCH_URL = "https://pixabay.com/api/videos/" |
|
|
CLIP_DURATION = 3.0 |
|
|
MAX_CLIPS = 5 |
|
|
|
|
|
def search_pixabay_videos(query, per_page=12): |
|
|
params = { |
|
|
"key": PIXABAY_API_KEY, |
|
|
"q": query, |
|
|
"video_type": "all", |
|
|
"per_page": per_page, |
|
|
"min_duration": int(CLIP_DURATION + 1), |
|
|
"safesearch": "true" |
|
|
} |
|
|
try: |
|
|
response = requests.get(VIDEO_SEARCH_URL, params=params, timeout=20) |
|
|
response.raise_for_status() |
|
|
data = response.json() |
|
|
videos = [] |
|
|
for hit in data.get("hits", []): |
|
|
vid = hit.get("videos", {}) |
|
|
if "large" in vid and vid["large"].get("url"): |
|
|
videos.append(vid["large"]["url"]) |
|
|
elif "medium" in vid and vid["medium"].get("url"): |
|
|
videos.append(vid["medium"]["url"]) |
|
|
return videos |
|
|
except Exception as e: |
|
|
st.error(f"خطا در جستجو: {e}") |
|
|
return [] |
|
|
|
|
|
@st.cache_data(show_spinner="در حال دانلود و پردازش ویدیوها...") |
|
|
def create_montage(query): |
|
|
if not query.strip(): |
|
|
return None, "کلمات کلیدی وارد کنید" |
|
|
|
|
|
query_clean = " ".join(query.strip().split()) |
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp_dir: |
|
|
video_urls = search_pixabay_videos(query_clean) |
|
|
|
|
|
if not video_urls: |
|
|
return None, f"هیچ ویدیویی برای '{query}' پیدا نشد 😔" |
|
|
|
|
|
clips = [] |
|
|
downloaded = 0 |
|
|
progress_bar = st.progress(0) |
|
|
status_text = st.empty() |
|
|
|
|
|
for i, url in enumerate(video_urls): |
|
|
if downloaded >= MAX_CLIPS: |
|
|
break |
|
|
|
|
|
status_text.text(f"در حال پردازش ویدیو {i+1}/{len(video_urls)} ...") |
|
|
|
|
|
try: |
|
|
|
|
|
resp = requests.get(url, stream=True, timeout=30) |
|
|
if resp.status_code != 200: |
|
|
continue |
|
|
|
|
|
temp_path = os.path.join(tmp_dir, f"video_{downloaded}.mp4") |
|
|
with open(temp_path, "wb") as f: |
|
|
shutil.copyfileobj(resp.raw, f) |
|
|
|
|
|
clip = VideoFileClip(temp_path) |
|
|
if clip.duration < CLIP_DURATION: |
|
|
clip.close() |
|
|
continue |
|
|
|
|
|
|
|
|
start = max(0, (clip.duration - CLIP_DURATION) / 2) |
|
|
subclip = clip.subclip(start, start + CLIP_DURATION) |
|
|
clips.append(subclip) |
|
|
downloaded += 1 |
|
|
|
|
|
clip.close() |
|
|
|
|
|
except Exception as e: |
|
|
st.warning(f"ویدیو {i+1} مشکل داشت: {e}") |
|
|
continue |
|
|
|
|
|
progress_bar.progress((i + 1) / len(video_urls)) |
|
|
|
|
|
progress_bar.empty() |
|
|
status_text.empty() |
|
|
|
|
|
if not clips: |
|
|
return None, "متأسفانه نتوانستیم ویدیو مناسب پردازش کنیم." |
|
|
|
|
|
try: |
|
|
final_clip = concatenate_videoclips(clips, method="compose") |
|
|
output_path = os.path.join(tmp_dir, "montage.mp4") |
|
|
|
|
|
with st.spinner("در حال ذخیره نهایی ویدیو..."): |
|
|
final_clip.write_videofile( |
|
|
output_path, |
|
|
codec="libx264", |
|
|
audio_codec="aac", |
|
|
fps=24, |
|
|
preset="medium", |
|
|
threads=2, |
|
|
logger=None |
|
|
) |
|
|
|
|
|
for c in clips: |
|
|
c.close() |
|
|
final_clip.close() |
|
|
|
|
|
return output_path, f"موفق! {len(clips)} کلیپ × {CLIP_DURATION} ثانیه" |
|
|
|
|
|
except Exception as e: |
|
|
return None, f"خطا در ساخت ویدیو: {str(e)}" |
|
|
|
|
|
|
|
|
st.set_page_config(page_title="مونتاژ ویدیو Pixabay", layout="wide") |
|
|
|
|
|
st.title("ساخت مونتاژ ویدیو از Pixabay 🎥") |
|
|
st.markdown("کلمات کلیدی را وارد کنید (مثال: sunset beach drone waves)") |
|
|
|
|
|
query = st.text_input( |
|
|
"کلمات کلیدی (با فاصله یا کاما جدا کنید)", |
|
|
placeholder="nature, mountain, timelapse, 4k", |
|
|
value="ocean sunset drone" |
|
|
) |
|
|
|
|
|
if st.button("ساخت مونتاژ (۳ ثانیه از هر کلیپ)", type="primary"): |
|
|
with st.spinner("در حال جستجو و ساخت ویدیو... این ممکن است ۳۰–۹۰ ثانیه طول بکشد"): |
|
|
video_path, message = create_montage(query) |
|
|
|
|
|
if video_path: |
|
|
st.success(message) |
|
|
st.video(video_path) |
|
|
else: |
|
|
st.error(message) |
|
|
|
|
|
st.markdown("---") |
|
|
st.caption("نکته: API Key را در Secrets بگذارید | حداکثر ۵ کلیپ | هر کلیپ ۳ ثانیه") |