# برنامه ادغام ویدیو بدون فاصله (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("""

🎬 ادغام ویدیو بدون فاصله

Video Merger - Seamless Clips

⭐ Built with anycoder

""") 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 )