Rockali's picture
Update app.py from anycoder
60ffa21 verified
# برنامه ادغام ویدیو بدون فاصله (Seamless Video Merger)
import gradio as gr
import os
import tempfile
from moviepy import VideoFileClip, concatenate_videoclips
import numpy as np
from typing import List, Optional
import shutil
def load_video_safe(path):
"""بارگذاری امن ویدیو با بررسی صحت فایل"""
try:
clip = VideoFileClip(path)
# بررسی معتبر بودن ویدیو
if clip.duration <= 0 or clip.fps <= 0:
clip.close()
return None
return clip
except Exception as e:
print(f"خطا در بارگذاری {path}: {e}")
return None
def create_smooth_transition(clip1, clip2, transition_type, duration=0.5):
"""ایجاد انتقال نرم بین دو ویدیو"""
if transition_type == "بدون انتقال (Cut)":
return clip2
elif transition_type == "محو شدن (Fade)":
# Fade out clip1 and fade in clip2
fade_duration = min(duration, clip1.duration * 0.3, clip2.duration * 0.3)
clip1_faded = clip1.fadeout(fade_duration)
clip2_faded = clip2.fadein(fade_duration)
return clip2_faded
elif transition_type == "حل شدن (Dissolve)":
# Cross dissolve effect
dissolve_duration = min(duration, clip1.duration * 0.3, clip2.duration * 0.3)
return clip1.crossfadein(dolve_duration)
elif transition_type == "انتقال سفید (White Flash)":
flash_duration = min(0.3, clip1.duration * 0.2, clip2.duration * 0.2)
return clip1.crossfadein(flash_duration)
elif transition_type == "انتقال سیاه (Black Fade)":
fade_duration = min(duration, clip1.duration * 0.3, clip2.duration * 0.3)
# Create black clip
from moviepy import ColorClip
black_clip = ColorClip(
size=clip1.size,
color=(0, 0, 0),
duration=fade_duration
).set_fps(clip1.fps)
# Sequence: clip1 -> black -> clip2
sequence = concatenate_videoclips([clip1, black_clip, clip2])
return sequence
else:
return clip2
def merge_videos(
video_files: List[str],
transition_type: str,
transition_duration: float,
remove_gaps: bool,
output_fps: Optional[int],
output_resolution: str,
output_quality: str
):
"""
ادغام ویدیوها بدون فاصله
Args:
video_files: لیست فایل‌های ویدیو
transition_type: نوع انتقال بین ویدیوها
transition_duration: مدت زمان انتقال
remove_gaps: حذف فاصله‌های بین ویدیوها
output_fps: فریم ریت خروجی
output_resolution: رزولوشن خروجی
output_quality: کیفیت خروجی
"""
if not video_files or len(video_files) < 1:
return None, "❌ لطفاً حداقل یک ویدیو آپلود کنید"
temp_dir = tempfile.mkdtemp()
output_path = os.path.join(temp_dir, "merged_video.mp4")
try:
# مرحله 1: بارگذاری تمام ویدیوها
print(f"در حال بارگذاری {len(video_files)} ویدیو...")
clips = []
for i, video_path in enumerate(video_files):
if video_path and os.path.exists(video_path):
clip = load_video_safe(video_path)
if clip:
clips.append((i, clip))
print(f" ویدیو {i+1}: {clip.duration:.2f} ثانیه، {clip.fps} fps")
if len(clips) == 0:
return None, "❌ هیچ ویدیوی معتبری یافت نشد"
# مرحله 2: مرتب‌سازی بر اساس ترتیب آپلود
clips.sort(key=lambda x: x[0])
clips = [clip for _, clip in clips]
# مرحله 3: تنظیم رزولوشن و fps همه ویدیوها به اولین ویدیو
reference_clip = clips[0]
target_size = reference_clip.size
target_fps = output_fps if output_fps else reference_clip.fps
# تنظیم رزولوشن بر اساس انتخاب کاربر
if output_resolution == "720p (1280x720)":
target_size = (1280, 720)
elif output_resolution == "1080p (1920x1080)":
target_size = (1920, 1080)
elif output_resolution == "480p (854x480)":
target_size = (854, 480)
# اگر "همانند اولین ویدیو" باشد، size تغییر نمی‌کند
processed_clips = []
for i, clip in enumerate(clips):
# تغییر سایز اگر نیاز باشد
if clip.size != target_size:
clip = clip.resized(new_size=target_size)
# تنظیم fps
if abs(clip.fps - target_fps) > 1:
clip = clip.with_fps(target_fps)
processed_clips.append(clip)
# مرحله 4: حذف فریم‌های خالی یا اضافی در انتها/ابتدا
if remove_gaps:
print("در حال حذف فاصله‌های اضافی...")
trimmed_clips = []
for i, clip in enumerate(processed_clips):
# حذف فریم‌های سیاه از ابتدا و انتها
start_trim = 0
end_trim = clip.duration
if end_trim > start_trim:
trimmed_clip = clip.subclipped(start_trim, end_trim)
trimmed_clips.append(trimmed_clip)
else:
trimmed_clips.append(clip)
processed_clips = trimmed_clips
# مرحله 5: ادغام ویدیوها با انتقال نرم
print("در حال ادغام ویدیوها...")
if len(processed_clips) == 1:
final_clip = processed_clips[0]
else:
merged_clips = [processed_clips[0]]
for i in range(1, len(processed_clips)):
clip1 = merged_clips[-1]
clip2 = processed_clips[i]
# کوتاه کردن انتهای clip1 و ابتدای clip2 برای حذف فاصله
if remove_gaps:
# حذف 0.1 ثانیه از انتهای clip1
if clip1.duration > 0.2:
clip1 = clip1.subclipped(0, clip1.duration - 0.1)
# حذف 0.1 ثانیه از ابتدای clip2
if clip2.duration > 0.2:
clip2 = clip2.subclipped(0.1, clip2.duration)
# ایجاد انتقال
transition_clip = create_smooth_transition(
clip1, clip2, transition_type, transition_duration
)
merged_clips.append(transition_clip)
final_clip = concatenate_videoclips(merged_clips, method="compose")
# مرحله 6: تنظیم کیفیت خروجی
print("در حال ذخیره ویدیوی نهایی...")
# انتخاب کیفیت بر اساس انتخاب کاربر
if output_quality == "عالی (Bitrate بالا)":
bitrate = "15M"
elif output_quality == "خوب (Bitrate متوسط)":
bitrate = "8M"
elif output_quality == "معمولی (Bitrate پایین)":
bitrate = "4M"
else:
bitrate = "8M" # پیش‌فرض
# ✅ اصلاح: حذف پارامتر verbose و logger (در MoviePy نسخه جدید پشتیبانی نمی‌شود)
# همچنین audio_codec به audio_fps تغییر کرده در نسخه‌های جدید
try:
# روش اول: MoviePy v2.x (جدید)
final_clip.write_videofile(
output_path,
codec='libx264',
bitrate=bitrate,
fps=target_fps,
audio=True,
audio_codec='aac',
)
except TypeError:
# روش دوم: MoviePy v1.x (قدیمی‌تر)
try:
final_clip.write_videofile(
output_path,
codec='libx264',
audio_codec='aac',
bitrate=bitrate,
fps=target_fps,
)
except TypeError as e:
if "audio_codec" in str(e):
# اگر audio_codec پشتیبانی نمی‌شود
final_clip.write_videofile(
output_path,
codec='libx264',
bitrate=bitrate,
fps=target_fps,
)
else:
raise e
# محاسبه آمار
original_duration = sum(clip.duration for clip in clips)
final_duration = final_clip.duration
# پاکسازی حافظه
for clip in clips:
clip.close()
final_clip.close()
# کپی به مسیر قابل دانلود
final_output = os.path.join(tempfile.gettempdir(), "merged_video_final.mp4")
shutil.copy(output_path, final_output)
message = f"""
✅ ویدیو با موفقیت ادغام شد!
📊 آمار:
• تعداد ویدیوها: {len(clips)}
• زمان کل (اصلی): {original_duration:.2f} ثانیه
• زمان کل (نهایی): {final_duration:.2f} ثانیه
• نوع انتقال: {transition_type}
• رزولوشن: {target_size[0]}x{target_size[1]}
• فریم ریت: {target_fps} fps
• کیفیت: {output_quality}
"""
return final_output, message
except Exception as e:
import traceback
traceback.print_exc()
return None, f"❌ خطا در پردازش: {str(e)}"
finally:
# پاکسازی فایل‌های موقت
try:
shutil.rmtree(temp_dir, ignore_errors=True)
except:
pass
def get_video_info(video_file):
"""دریافت اطلاعات یک ویدیو"""
if not video_file or not os.path.exists(video_file):
return "❌ فایل یافت نشد"
try:
clip = VideoFileClip(video_file)
info = f"""
📹 اطلاعات ویدیو:
• مدت زمان: {clip.duration:.2f} ثانیه
• رزولوشن: {clip.size[0]}x{clip.size[1]}
• فریم ریت: {clip.fps} fps
• صدا: {'دارد' if clip.audio else 'ندارد'}
"""
clip.close()
return info
except Exception as e:
return f"❌ خطا: {str(e)}"
# ایجاد تم سفارشی
custom_theme = gr.themes.Soft(
primary_hue="blue",
secondary_hue="indigo",
neutral_hue="slate",
font=gr.themes.GoogleFont("Vazirmatn"),
text_size="lg",
spacing_size="lg",
radius_size="md"
).set(
button_primary_background_fill="*primary_600",
button_primary_background_fill_hover="*primary_700",
block_title_text_weight="600",
body_text_weight="400",
)
# CSS سفارشی برای پشتیبانی از زبان فارسی
custom_css = """
.gradio-container {
direction: rtl;
font-family: 'Vazirmatn', 'Tahoma', sans-serif !important;
}
.gap-4 {
gap: 1rem !important;
}
.prose {
text-align: right !important;
}
#error_message {
background-color: #fef2f2 !important;
border-color: #ef4444 !important;
}
#success_message {
background-color: #f0fdf4 !important;
border-color: #22c55e !important;
}
"""
# ساخت رابط کاربری
# ✅ GRADIO 6: gr.Blocks() بدون پارامتر
with gr.Blocks() as demo:
# هدر با لینک anycoder
gr.HTML("""
<div style="text-align: center; padding: 10px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; margin-bottom: 20px;">
<h1 style="color: white; margin: 0; font-size: 2em;">🎬 ادغام ویدیو بدون فاصله</h1>
<p style="color: rgba(255,255,255,0.9); margin: 10px 0 0 0;">Video Merger - Seamless Clips</p>
<p style="color: rgba(255,255,255,0.7); font-size: 0.9em; margin: 5px 0 0 0;">
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="color: #ffd700;">⭐ Built with anycoder</a>
</p>
</div>
""")
with gr.Row():
with gr.Column(scale=2):
# بخش آپلود ویدیوها
gr.Markdown("## 📤 آپلود ویدیوها", elem_classes=["prose"])
video_uploader = gr.File(
label="انتخاب ویدیوها (می‌توانید چندین فایل انتخاب کنید)",
file_types=["video"],
file_count="multiple",
height=150,
elem_id="video_uploader"
)
# نمایش تعداد ویدیوهای انتخاب شده
video_count = gr.Number(value=0, label="تعداد ویدیوها", interactive=False)
video_uploader.change(
lambda files: len(files) if files else 0,
video_uploader,
video_count
)
# اطلاعات ویدیوها
gr.Markdown("### ℹ️ اطلاعات ویدیوهای انتخاب شده:")
video_info = gr.Textbox(
value="",
label="",
lines=5,
interactive=False,
elem_id="video_info"
)
with gr.Column(scale=1):
# تنظیمات انتقال
gr.Markdown("## ⚙️ تنظیمات", elem_classes=["prose"])
transition_type = gr.Dropdown(
choices=[
"بدون انتقال (Cut)",
"محو شدن (Fade)",
"حل شدن (Dissolve)",
"انتقال سیاه (Black Fade)",
"انتقال سفید (White Flash)"
],
value="بدون انتقال (Cut)",
label="نوع انتقال بین ویدیوها",
info="برای حذف کامل فاصله، 'بدون انتقال' را انتخاب کنید"
)
transition_duration = gr.Slider(
minimum=0.1,
maximum=2.0,
value=0.3,
step=0.1,
label="مدت زمان انتقال (ثانیه)",
info="فقط برای انتقال‌های Fade و Dissolve"
)
remove_gaps = gr.Checkbox(
value=True,
label="حذف فاصله‌های اضافی",
info="فریم‌های خالی ابتدا و انتهای هر کلیپ را حذف می‌کند"
)
# تنظیمات خروجی
gr.Markdown("### 🎯 تنظیمات خروجی", elem_classes=["prose"])
output_resolution = gr.Dropdown(
choices=[
"همانند اولین ویدیو",
"480p (854x480)",
"720p (1280x720)",
"1080p (1920x1080)"
],
value="همانند اولین ویدیو",
label="رزولوشن خروجی"
)
output_fps = gr.Dropdown(
choices=["24", "30", "60", "همانند اولین ویدیو"],
value="همانند اولین ویدیو",
label="فریم ریت خروجی"
)
output_quality = gr.Dropdown(
choices=["خوب (Bitrate متوسط)", "عالی (Bitrate بالا)", "معمولی (Bitrate پایین)"],
value="خوب (Bitrate متوسط)",
label="کیفیت خروجی"
)
# دکمه شروع ادغام
merge_btn = gr.Button(
"🚀 ادغام ویدیوها",
variant="primary",
size="lg",
elem_id="merge_btn"
)
# نوار پیشرفت
progress_bar = gr.Progress()
# پیام وضعیت
status_message = gr.Textbox(
label="پیام سیستم",
lines=3,
interactive=False,
elem_id="status_message"
)
# ویدیوی خروجی
gr.Markdown("## 🎉 ویدیوی ادغام شده", elem_classes=["prose"])
output_video = gr.Video(
label="ویدیوی نهایی",
height=400,
format="mp4"
)
# دکمه دانلود
download_btn = gr.DownloadButton(
value=None,
label="⬇️ دانلود ویدیو",
variant="secondary",
size="lg",
visible=False,
elem_id="download_btn"
)
# تابع ادغام با progress
def merge_with_progress(
video_files, transition_type, transition_duration,
remove_gaps, output_fps, output_resolution, output_quality, progress=gr.Progress()
):
progress(0, desc="شروع...")
if not video_files or len(video_files) == 0:
return None, None, "❌ لطفاً ویدیو انتخاب کنید", gr.update(visible=False)
# تبدیل fps
fps_value = None
if output_fps != "همانند اولین ویدیو":
fps_value = int(output_fps)
progress(0.1, desc="در حال پردازش...")
# فراخوانی تابع اصلی
output_path, message = merge_videos(
video_files,
transition_type,
transition_duration,
remove_gaps,
fps_value,
output_resolution,
output_quality
)
progress(1.0, desc="تکمیل!")
if output_path:
return output_path, output_path, message, gr.update(value=output_path, visible=True)
else:
return None, None, message, gr.update(visible=False)
# اتصال رویدادها
merge_btn.click(
merge_with_progress,
inputs=[
video_uploader, transition_type, transition_duration,
remove_gaps, output_fps, output_resolution, output_quality
],
outputs=[output_video, download_btn, status_message, download_btn],
show_progress="full"
)
# راهنما
with gr.Accordion("📖 راهنما و نکات مهم", open=True):
gr.Markdown("""
### 🔧 نحوه استفاده:
1. **آپلود ویدیوها**: روی دکمه کلیک کنید و ویدیوهای خود را انتخاب کنید (می‌توانید چندین فایل را همزمان انتخاب کنید)
2. **ترتیب ویدیوها**: ترتیب آپلود = ترتیب ادغام
3. **نوع انتقال**:
- **بدون انتقال (Cut)**: ویدیوها مستقیم به هم وصل می‌شوند (بهترین برای حذف فاصله)
- **Fade/Dissolve**: انتقال نرم بین ویدیوها
4. **حذف فاصله‌ها**: تیک "حذف فاصله‌های اضافی" را فعال کنید
5. **ادغام**: روی دکمه "ادغام ویدیوها" کلیک کنید
6. **دانلود**: ویدیوی نهایی را دانلود کنید
### ⚡ نکات مهم:
- فرمت خروجی: **MP4**
- کدک: **H.264** (سازگار با تمام پلتفرم‌ها)
- صدا: **AAC** (22050Hz)
- حداکثر اندازه فایل: 2GB
""")
# ✅ GRADIO 6: تمام پارامترها در launch() قرار می‌گیرند
demo.launch(
theme=custom_theme,
css=custom_css,
footer_links=[
{"label": "Built with anycoder", "url": "https://huggingface.co/spaces/akhaliq/anycoder"}
],
server_port=7860,
debug=True
)