import gradio as gr import base64 import mimetypes import os import re import struct import time import zipfile from google import genai from google.genai import types import traceback # برای نمایش خطاهای دقیقتر # خواندن کلید API از Hugging Face Secrets HF_GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY") try: from pydub import AudioSegment PYDUB_AVAILABLE = True except ImportError: PYDUB_AVAILABLE = False print("⚠️ کتابخانه pydub در دسترس نیست. قابلیت ادغام فایلهای صوتی غیرفعال خواهد بود.") # --- ثابتها --- SPEAKER_VOICES = [ "Achird", "Zubenelgenubi", "Vindemiatrix", "Sadachbia", "Sadaltager", "Sulafat", "Laomedeia", "Achernar", "Alnilam", "Schedar", "Gacrux", "Pulcherrima", "Umbriel", "Algieba", "Despina", "Erinome", "Algenib", "Rasalthgeti", "Orus", "Aoede", "Callirrhoe", "Autonoe", "Enceladus", "Iapetus", "Zephyr", "Puck", "Charon", "Kore", "Fenrir", "Leda" ] MODELS = ["gemini-2.5-flash-preview-tts", "gemini-2.5-pro-preview-tts"] MODEL_NAMES_FARSI = { "gemini-2.5-flash-preview-tts": "جمینای ۲.۵ فلش (سریعتر، اقتصادیتر)", "gemini-2.5-pro-preview-tts": "جمینای ۲.۵ پرو (کیفیت بالاتر)" } SPEAKER_VOICES_FARSI_SAMPLE = { # میتوانید برای همه گویندهها نام فارسی تعریف کنید "Charon": "شارون (پیشفرض)", "Achernar": "آخرالنهر", "Vindemiatrix": "vindemiatrix (ستارهشناس)", # ... بقیه گویندهها } # --- توابع کمکی (بدون تغییر زیاد در منطق، فقط پیامها فارسی میشوند) --- def save_binary_file(file_name, data): abs_file_name = os.path.abspath(file_name) try: with open(abs_file_name, "wb") as f: f.write(data) print(f"✅ فایل در مسیر ذخیره شد: {abs_file_name}") return abs_file_name except Exception as e: print(f"❌ خطا در ذخیره فایل {abs_file_name}: {e}") return None def convert_to_wav(audio_data: bytes, mime_type: str) -> bytes: parameters = parse_audio_mime_type(mime_type) bits_per_sample = parameters["bits_per_sample"] sample_rate = parameters["rate"] num_channels = 1 data_size = len(audio_data) bytes_per_sample = bits_per_sample // 8 block_align = num_channels * bytes_per_sample byte_rate = sample_rate * block_align chunk_size = 36 + data_size header = struct.pack( "<4sI4s4sIHHIIHH4sI", b"RIFF", chunk_size, b"WAVE", b"fmt ", 16, 1, num_channels, sample_rate, byte_rate, block_align, bits_per_sample, b"data", data_size ) return header + audio_data def parse_audio_mime_type(mime_type: str) -> dict[str, int | None]: bits_per_sample = 16 rate = 24000 if mime_type: mime_type_lower = mime_type.lower() parts = mime_type_lower.split(";") for param in parts: param = param.strip() if param.startswith("rate="): try: rate = int(param.split("=", 1)[1]) except: pass elif param.startswith("audio/l"): try: potential_bits = param.split("l", 1)[1] if potential_bits.isdigit(): bits_per_sample = int(potential_bits) except: pass return {"bits_per_sample": bits_per_sample, "rate": rate} def load_text_from_gr_file(file_obj): if file_obj is None: return "", "فایلی برای ورودی متن ارائه نشده است." try: with open(file_obj.name, 'r', encoding='utf-8') as f: content = f.read().strip() if not content: return "", "فایل متنی خالی است." return content, f"متن با موفقیت از فایل '{os.path.basename(file_obj.name)}' ({len(content)} کاراکتر) بارگذاری شد." except Exception as e: return "", f"خطا در خواندن فایل متنی: {e}" def smart_text_split(text, max_size=3800): if len(text) <= max_size: return [text] chunks, current_chunk = [], "" sentences = re.split(r'(?<=[.!?؟])\s+', text) for sentence in sentences: if not sentence: continue if len(current_chunk) + len(sentence) + 1 > max_size: if current_chunk: chunks.append(current_chunk.strip()) if len(sentence) > max_size: words, temp_part = sentence.split(' '), "" for word in words: if len(temp_part) + len(word) + 1 > max_size: if temp_part: chunks.append(temp_part.strip()) if len(word) > max_size: for i in range(0, len(word), max_size): chunks.append(word[i:i+max_size]) temp_part = "" else: temp_part = word else: temp_part += (" " if temp_part else "") + word if temp_part: chunks.append(temp_part.strip()) current_chunk = "" else: current_chunk = sentence else: current_chunk += (" " if current_chunk else "") + sentence if current_chunk: chunks.append(current_chunk.strip()) return chunks def merge_audio_files_func(file_paths, output_path): if not PYDUB_AVAILABLE: return False, "pydub در دسترس نیست. امکان ادغام فایلها وجود ندارد.", None if not file_paths: return False, "هیچ فایل صوتی برای ادغام وجود ندارد.", None try: combined = AudioSegment.empty() for i, file_path in enumerate(file_paths): if os.path.exists(file_path): try: audio = AudioSegment.from_file(file_path, format="wav") # فرض میکنیم ورودیها WAV هستند combined += audio if i < len(file_paths) - 1: combined += AudioSegment.silent(duration=200) except Exception as e_load: msg = f"خطا در بارگذاری فایل صوتی '{os.path.basename(file_path)}' با pydub: {e_load}" print(f"⚠️ {msg}") return False, msg, None else: msg = f"فایل برای ادغام یافت نشد: {os.path.basename(file_path)}" print(f"⚠️ {msg}") return False, msg, None abs_output_path = os.path.abspath(output_path) combined.export(abs_output_path, format="wav") return True, f"فایل ادغام شده با موفقیت در '{os.path.basename(abs_output_path)}' ذخیره شد.", abs_output_path except Exception as e: msg = f"خطا در ادغام فایلها: {e}" print(f"❌ {msg}") return False, msg, None def create_zip_file(file_paths, zip_name): abs_zip_name = os.path.abspath(zip_name) try: with zipfile.ZipFile(abs_zip_name, 'w') as zipf: for file_path in file_paths: if os.path.exists(file_path): zipf.write(file_path, os.path.basename(file_path)) return True, f"فایل ZIP با نام '{os.path.basename(abs_zip_name)}' ایجاد شد.", abs_zip_name except Exception as e: return False, f"خطا در ایجاد فایل ZIP: {e}", None # --- تابع اصلی تولید صدا --- def generate_audio_for_gradio( use_file_input_checkbox, text_file_obj, speech_prompt_input, text_to_speak_input, max_chunk_slider, sleep_slider, temperature_slider, model_dropdown_key, # کلید مدل (انگلیسی) speaker_dropdown, output_filename_base_input, merge_checkbox, delete_partials_checkbox, progress=gr.Progress(track_tqdm=True) ): status_messages = [] status_messages.append("🚀 فرآیند تبدیل متن به گفتار آغاز شد...") progress(0, desc="در حال آمادهسازی...") api_key_to_use = HF_GEMINI_API_KEY if not api_key_to_use: status_messages.append("❌ خطا: کلید API جمینای (GEMINI_API_KEY) در تنظیمات Secret این Space یافت نشد.") status_messages.append("⬅️ لطفاً آن را در بخش Settings > Secrets مربوط به این Space تنظیم کنید.") return None, None, "\n".join(status_messages) os.environ["GEMINI_API_KEY"] = api_key_to_use status_messages.append("🔑 کلید API با موفقیت از Secrets بارگذاری شد.") actual_text_input = "" if use_file_input_checkbox: if text_file_obj is None: status_messages.append("❌ خطا: گزینه 'استفاده از فایل متنی' انتخاب شده، اما هیچ فایلی آپلود نشده است.") return None, None, "\n".join(status_messages) actual_text_input, msg = load_text_from_gr_file(text_file_obj) status_messages.append(msg) if not actual_text_input: return None, None, "\n".join(status_messages) else: actual_text_input = text_to_speak_input status_messages.append("⌨️ از متن وارد شده به صورت دستی استفاده میشود.") if not actual_text_input or actual_text_input.strip() == "": status_messages.append("❌ خطا: متن ورودی خالی است.") return None, None, "\n".join(status_messages) try: status_messages.append("🛠️ در حال مقداردهی اولیه کلاینت جمینای...") progress(0.1, desc="اتصال به جمینای...") client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY")) status_messages.append("✅ کلاینت جمینای با موفقیت ایجاد شد.") except Exception as e: status_messages.append(f"❌ خطا در ایجاد کلاینت جمینای: {e}") return None, None, "\n".join(status_messages) text_chunks = smart_text_split(actual_text_input, int(max_chunk_slider)) status_messages.append(f"📊 متن به {len(text_chunks)} قطعه تقسیم شد.") for i, chunk_text_content in enumerate(text_chunks): status_messages.append(f" 📝 قطعه {i+1}: {len(chunk_text_content)} کاراکتر") generated_audio_files = [] run_id = base64.urlsafe_b64encode(os.urandom(6)).decode() temp_output_dir = f"temp_audio_{run_id}" os.makedirs(temp_output_dir, exist_ok=True) output_base_name_safe = re.sub(r'[\s\\\/\:\*\?\"\<\>\|\%]+', '_', output_filename_base_input) total_chunks = len(text_chunks) for i, chunk_text_content in enumerate(text_chunks): progress_val = 0.1 + (0.7 * (i / total_chunks)) progress(progress_val, desc=f"در حال تولید قطعه {i+1} از {total_chunks}...") status_messages.append(f"\n🔊 در حال تولید صدا برای قطعه {i+1}/{total_chunks}...") final_text_for_api = f'"{speech_prompt_input}"\n{chunk_text_content}' if speech_prompt_input.strip() else chunk_text_content contents_for_api = [types.Content(role="user", parts=[types.Part.from_text(text=final_text_for_api)])] generate_content_config = types.GenerateContentConfig( temperature=float(temperature_slider), response_modalities=["audio"], speech_config=types.SpeechConfig( voice_config=types.VoiceConfig( prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name=speaker_dropdown) # گوینده از ورودی ) ) ) try: chunk_filename_base = f"{output_base_name_safe}_part_{i+1:03d}" chunk_filepath_prefix = os.path.join(temp_output_dir, chunk_filename_base) audio_data_received = False for stream_response_chunk in client.models.generate_content_stream( model=model_dropdown_key, contents=contents_for_api, config=generate_content_config, ): if (stream_response_chunk.candidates and stream_response_chunk.candidates[0].content and stream_response_chunk.candidates[0].content.parts and stream_response_chunk.candidates[0].content.parts[0].inline_data): inline_data = stream_response_chunk.candidates[0].content.parts[0].inline_data data_buffer = inline_data.data api_mime_type = inline_data.mime_type audio_data_received = True status_messages.append(f"ℹ️ MIME Type دریافتی از API: {api_mime_type}") file_extension = ".wav" if api_mime_type and ("mp3" in api_mime_type.lower() or "mpeg" in api_mime_type.lower()): file_extension = ".mp3" status_messages.append(f"ℹ️ ذخیره با فرمت MP3 بر اساس MIME Type: {api_mime_type}") elif api_mime_type and "wav" in api_mime_type.lower() and \ not ("audio/l16" in api_mime_type.lower() or "audio/l24" in api_mime_type.lower()): file_extension = ".wav" status_messages.append(f"ℹ️ ذخیره با فرمت WAV بر اساس MIME Type: {api_mime_type}") else: file_extension = ".wav" status_messages.append(f"ℹ️ تبدیل به فرمت WAV برای MIME Type: {api_mime_type or 'نامشخص'}") data_buffer = convert_to_wav(data_buffer, api_mime_type) status_messages.append(f"ℹ️ پسوند فایل نهایی: {file_extension}") generated_file_path = save_binary_file(f"{chunk_filepath_prefix}{file_extension}", data_buffer) if generated_file_path: generated_audio_files.append(generated_file_path) status_messages.append(f"✅ قطعه {i+1} ذخیره شد: {os.path.basename(generated_file_path)}") else: status_messages.append(f"❌ عدم موفقیت در ذخیره قطعه {i+1}.") break elif stream_response_chunk.text: status_messages.append(f"ℹ️ پیام متنی از API (حین استریم): {stream_response_chunk.text}") if not audio_data_received: status_messages.append(f"❌ هیچ داده صوتی برای قطعه {i+1} دریافت نشد.") if stream_response_chunk and stream_response_chunk.prompt_feedback and stream_response_chunk.prompt_feedback.block_reason: status_messages.append(f"🛑 دلیل مسدود شدن توسط API: {stream_response_chunk.prompt_feedback.block_reason_message or stream_response_chunk.prompt_feedback.block_reason}") except types.BlockedPromptException as bpe: status_messages.append(f"❌ محتوای قطعه {i+1} توسط API مسدود شد: {bpe}") status_messages.append(f" بازخورد API: {bpe.response.prompt_feedback}") except types.StopCandidateException as sce: status_messages.append(f"❌ تولید صدا برای قطعه {i+1} متوقف شد: {sce}") status_messages.append(f" بازخورد API: {sce.response.prompt_feedback}") except Exception as e: status_messages.append(f"❌ خطا در تولید/پردازش قطعه {i+1}: {type(e).__name__} - {e}") status_messages.append(traceback.format_exc()) continue if i < total_chunks - 1 and float(sleep_slider) > 0 : status_messages.append(f"⏱️ انتظار به مدت {sleep_slider} ثانیه...") time.sleep(float(sleep_slider)) progress(0.85, desc="پردازش فایلهای نهایی...") if not generated_audio_files: status_messages.append("❌ هیچ فایل صوتی با موفقیت تولید یا ذخیره نشد!") final_status = "\n".join(status_messages) print(final_status) progress(1, desc="پایان با خطا.") return None, None, final_status status_messages.append(f"\n🎉 {len(generated_audio_files)} فایل(های) صوتی تولید شد!") output_audio_path_for_player = None output_path_for_download = None if merge_checkbox and len(generated_audio_files) > 1 and PYDUB_AVAILABLE: status_messages.append(f"🔗 در حال ادغام {len(generated_audio_files)} فایل صوتی...") merged_filename_path = os.path.join(temp_output_dir, f"{output_base_name_safe}_merged.wav") success_merge, msg_merge, merged_p = merge_audio_files_func(generated_audio_files, merged_filename_path) status_messages.append(msg_merge) if success_merge: output_audio_path_for_player = merged_p output_path_for_download = merged_p if delete_partials_checkbox: status_messages.append("🗑️ در حال حذف فایلهای جزئی...") for file_p in generated_audio_files: try: os.remove(file_p); status_messages.append(f" 🗑️ حذف شد: {os.path.basename(file_p)}") except Exception as e_del: status_messages.append(f" ⚠️ عدم موفقیت در حذف {os.path.basename(file_p)}: {e_del}") else: status_messages.append("⚠️ ادغام ناموفق بود. فایل ZIP از قطعات ارائه میشود.") success_zip, msg_zip, zip_p = create_zip_file(generated_audio_files, os.path.join(temp_output_dir, f"{output_base_name_safe}_all_parts.zip")) status_messages.append(msg_zip) if success_zip: output_path_for_download = zip_p elif len(generated_audio_files) == 1: single_file_path = generated_audio_files[0] output_audio_path_for_player = single_file_path output_path_for_download = single_file_path status_messages.append(f"🎵 فایل صوتی تکی: {os.path.basename(single_file_path)}") elif len(generated_audio_files) > 1: # No merge or pydub not available if not PYDUB_AVAILABLE and merge_checkbox: status_messages.append("⚠️ pydub در دسترس نیست، امکان ادغام وجود ندارد. فایل ZIP ارائه میشود.") status_messages.append("📦 چندین قطعه تولید شد. در حال ایجاد فایل ZIP...") success_zip, msg_zip, zip_p = create_zip_file(generated_audio_files, os.path.join(temp_output_dir, f"{output_base_name_safe}_all_parts.zip")) status_messages.append(msg_zip) if success_zip: output_path_for_download = zip_p final_status = "\n".join(status_messages) print(final_status) print(f"DEBUG مسیر فایل برای پخش کننده: {output_audio_path_for_player}") print(f"DEBUG مسیر فایل برای دانلود: {output_path_for_download}") progress(1, desc="انجام شد!") return output_audio_path_for_player, output_path_for_download, final_status # --- تعریف رابط کاربری Gradio --- with gr.Blocks(theme=gr.themes.Soft(primary_hue=gr.themes.colors.blue, secondary_hue=gr.themes.colors.sky), title="تبدیل متن به گفتار با Gemini") as demo: gr.Markdown("# 🎵 تبدیل متن به گفتار با Gemini API 🗣️", elem_id="app-title") if not HF_GEMINI_API_KEY: gr.Warning( "کلید API جمینای (GEMINI_API_KEY) در Hugging Face Secrets یافت نشد. " "لطفاً آن را در بخش 'Settings' > 'Secrets' این Space با نام `GEMINI_API_KEY` اضافه کنید تا برنامه کار کند." ) else: gr.Info("کلید API جمینای با موفقیت از Secrets بارگذاری شد. آماده تولید صدا!") gr.Markdown( "این ابزار متن شما را با استفاده از API قدرتمند Gemini گوگل به گفتار تبدیل میکند. " "برای استفاده، باید کلید API جمینای خود را در بخش Secrets این Space تنظیم کرده باشید." "\n\nمیتوانید کلید API خود را از [استودیوی هوش مصنوعی گوگل (Google AI Studio)](https://aistudio.google.com/app/apikey) دریافت کنید." ) with gr.Row(): with gr.Column(scale=3): # ستون ورودی متن gr.Markdown("### ۱. متن ورودی") use_file = gr.Checkbox(label="📁 استفاده از فایل متنی (.txt) به جای ورود دستی", value=False) text_file = gr.File( label="آپلود فایل متنی", file_types=['.txt'], visible=False ) text_to_speak = gr.Textbox( label="📝 متنی که میخواهید به گفتار تبدیل شود:", lines=10, placeholder="متن خود را در اینجا وارد کنید یا فایل متنی را در بالا آپلود نمایید...", visible=True, text_align="right" # برای متون فارسی ) use_file.change( lambda x: (gr.update(visible=x), gr.update(visible=not x)), [use_file], [text_file, text_to_speak] ) speech_prompt = gr.Textbox( label="🗣️ فرمان سبک گفتار (اختیاری)", placeholder="مثال: «با لحنی دوستانه و پرانرژی، مانند یک مجری پادکست صحبت کن»", info="این فرمان به تنظیم سبک، احساسات و ویژگیهای صدای خروجی کمک میکند.", text_align="right" ) with gr.Column(scale=2): # ستون تنظیمات gr.Markdown("### ۲. تنظیمات تولید صدا") # تبدیل دیکشنری نامهای فارسی به لیست تاپلها برای Dropdown model_choices_farsi = [(MODEL_NAMES_FARSI[key], key) for key in MODELS] model_name_dropdown = gr.Dropdown( choices=model_choices_farsi, # نمایش نام فارسی، ارسال کلید انگلیسی label="🤖 انتخاب مدل Gemini", value=MODELS[0] # مقدار پیشفرض کلید انگلیسی ) speaker_choices_farsi = [(SPEAKER_VOICES_FARSI_SAMPLE.get(v, v), v) for v in SPEAKER_VOICES] speaker_voice_dropdown = gr.Dropdown( choices=speaker_choices_farsi, label="🎤 انتخاب گوینده", value="Charon" ) temperature_slider = gr.Slider( minimum=0.0, maximum=1.0, step=0.05, value=0.7, label="🌡️ دمای مدل (Temperature)", info="میزان خلاقیت و تنوع در خروجی (0.0 تا 1.0). مقادیر بالاتر = تنوع بیشتر." ) max_chunk_size_slider = gr.Slider( minimum=1000, maximum=4000, step=100, value=3800, label="🧩 حداکثر کاراکتر در هر قطعه", info="متن برای ارسال به API به قطعات کوچکتر تقسیم میشود." ) sleep_between_requests_slider = gr.Slider( minimum=0, maximum=15, step=0.5, value=1, # کاهش مقدار پیشفرض برای سرعت بیشتر label="⏱️ تاخیر بین درخواستها (ثانیه)", info="برای مدیریت محدودیتهای API (مثلاً Gemini Flash دارای محدودیت ۶۰ درخواست در دقیقه است)." ) output_filename_base_input = gr.Textbox( label="💾 نام پایه فایل خروجی", value="gemini_tts_farsi" ) with gr.Group(elem_id="merge-options"): gr.Markdown("تنظیمات ادغام (در صورت تولید بیش از یک قطعه):") merge_audio_checkbox = gr.Checkbox(label="🔗 ادغام قطعات صوتی", value=True, visible=PYDUB_AVAILABLE) delete_partials_checkbox = gr.Checkbox(label="🗑️ حذف قطعات پس از ادغام", value=True, visible=PYDUB_AVAILABLE and True) # نمایش اگر ادغام فعال و pydub موجود باشد if PYDUB_AVAILABLE: merge_audio_checkbox.change(lambda x: gr.update(visible=x), [merge_audio_checkbox], [delete_partials_checkbox]) else: gr.Markdown("
⚠️ قابلیت ادغام فایلها به دلیل عدم دسترسی به کتابخانه `pydub` غیرفعال است.
") submit_button = gr.Button("✨ تولید فایل صوتی ✨", variant="primary", elem_id="submit-button-main") gr.Markdown("### ۳. خروجی") with gr.Row(): with gr.Column(scale=1): output_audio_player_component = gr.Audio(label="🎧 فایل صوتی تولید شده (قابل پخش)", type="filepath") with gr.Column(scale=1): output_file_download_component = gr.File(label="📥 دانلود فایل خروجی (صوتی یا ZIP)", type="filepath") status_textbox_component = gr.Textbox(label="📊 گزارش وضعیت و پیامها", lines=10, interactive=False, max_lines=20, text_align="right") submit_button.click( fn=generate_audio_for_gradio, inputs=[ use_file, text_file, speech_prompt, text_to_speak, max_chunk_size_slider, sleep_between_requests_slider, temperature_slider, model_name_dropdown, speaker_voice_dropdown, output_filename_base_input, merge_audio_checkbox, delete_partials_checkbox ], outputs=[output_audio_player_component, output_file_download_component, status_textbox_component] ) gr.Markdown("---") # اطلاعات سازنده encoded_text_creator = "Q3JlYXRlZCBieSA6IEhhbWVkNzQ0IChBSUdPTERFTikgZm9yIEh1Z2dpbmcgRmFjZSBTcGFjZXMu" # "Created by : Hamed744 (AIGOLDEN) for Hugging Face Spaces." try: decoded_text_creator = base64.b64decode(encoded_text_creator.encode('utf-8')).decode('utf-8') gr.Markdown(f"{decoded_text_creator}
") except Exception: pass gr.Examples( label="چند مثال برای شروع:", examples=[ [False, None, "یک راوی مهربان و آموزنده.", "سلام دنیا! این یک آزمایش برای تبدیل متن به گفتار با جمینای در گریدیا است. امیدوارم خوب کار کند!", 3800, 1, 0.7, MODELS[0], "Charon", "hello_farsi", True, True], [False, None, "یک گزارشگر خبری هیجانزده.", "خبر فوری! هوش مصنوعی اکنون میتواند گفتاری شبیه به انسان با وضوح باورنکردنی تولید کند. این فناوری به سرعت در حال پیشرفت است!", 3000, 1, 0.8, MODELS[1], "Achernar", "news_farsi", True, True], ], fn=generate_audio_for_gradio, inputs=[ use_file, text_file, speech_prompt, text_to_speak, max_chunk_size_slider, sleep_between_requests_slider, temperature_slider, model_name_dropdown, speaker_voice_dropdown, output_filename_base_input, merge_audio_checkbox, delete_partials_checkbox ], outputs=[output_audio_player_component, output_file_download_component, status_textbox_component], cache_examples=False # چون با API کار میکند و ورودیها داینامیک هستند ) # اجرای برنامه در صورت اجرای مستقیم فایل (برای تست محلی) if __name__ == "__main__": if not PYDUB_AVAILABLE: print("هشدار: کتابخانه pydub نصب نشده یا کار نمیکند. قابلیت ادغام فایلهای صوتی غیرفعال خواهد بود.") if not HF_GEMINI_API_KEY: print("هشدار: متغیر محیطی GEMINI_API_KEY تنظیم نشده است. اگر برنامه برای کلید API به آن متکی باشد، ممکن است در حالت محلی کار نکند.") demo.launch(debug=True, share=False) # share=False برای اجرای محلی، هاگینگ فیس لینک عمومی را مدیریت میکند