citoreh commited on
Commit
c1c5ae6
·
verified ·
1 Parent(s): 5ef359a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +530 -108
app.py CHANGED
@@ -1,12 +1,41 @@
1
  import gradio as gr
 
 
2
  import re
3
  import warnings
 
 
 
 
4
  warnings.filterwarnings("ignore")
5
 
6
- class LightweightPersianSummarizer:
7
  def __init__(self):
8
- """Lightweight Persian summarizer using extractive methods"""
9
- print("Initializing lightweight Persian summarizer...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  def preprocess_persian_text(self, text):
12
  """Clean and preprocess Persian text"""
@@ -18,102 +47,400 @@ class LightweightPersianSummarizer:
18
 
19
  return text
20
 
21
- def score_sentence(self, sentence, position, total_sentences):
22
- """Score sentences for importance"""
23
- # Position score (earlier sentences are more important)
24
- position_score = 1.0 - (position / total_sentences) * 0.3
25
-
26
- # Length score (moderate length sentences are preferred)
27
- length = len(sentence.strip())
28
- if length < 20:
29
- length_score = 0.1
30
- elif length > 200:
31
- length_score = 0.7
32
- else:
33
- length_score = 1.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
- # Keyword density score (sentences with more Persian content)
36
- persian_chars = len(re.findall(r'[\u0600-\u06FF]', sentence))
37
- total_chars = len(sentence.replace(' ', ''))
38
- persian_ratio = persian_chars / max(total_chars, 1)
 
39
 
40
- # Combined score
41
- final_score = (position_score * 0.4) + (length_score * 0.3) + (persian_ratio * 0.3)
 
 
 
 
 
 
 
42
 
43
- return final_score
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
- def extractive_summary(self, text, target_length):
46
- """Advanced extractive summarization for Persian text"""
47
- # Split into sentences
48
- sentences = re.split(r'[.!?؟]', text)
49
- sentences = [s.strip() for s in sentences if len(s.strip()) > 15]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- if len(sentences) <= 2:
52
- return text[:target_length] + ("..." if len(text) > target_length else "")
 
53
 
54
- # Score all sentences
55
- scored_sentences = []
56
- for i, sentence in enumerate(sentences):
57
- score = self.score_sentence(sentence, i, len(sentences))
58
- scored_sentences.append((sentence, score, i))
59
 
60
- # Sort by score (highest first)
61
- scored_sentences.sort(key=lambda x: x[1], reverse=True)
 
62
 
63
- # Select sentences up to target length
64
- selected_sentences = []
65
- current_length = 0
 
 
 
66
 
67
- for sentence, score, original_index in scored_sentences:
68
- sentence_length = len(sentence)
69
- if current_length + sentence_length <= target_length:
70
- selected_sentences.append((sentence, original_index))
71
- current_length += sentence_length
72
- elif len(selected_sentences) == 0: # At least include one sentence
73
- # Truncate the sentence if needed
74
- truncated = sentence[:target_length-10] + "..."
75
- selected_sentences.append((truncated, original_index))
76
  break
77
 
78
- # Sort selected sentences by original order
79
- selected_sentences.sort(key=lambda x: x[1])
 
 
 
 
 
80
 
81
- # Join sentences
82
- summary = ". ".join([sent[0] for sent in selected_sentences])
83
 
84
- # Final cleanup
85
- summary = re.sub(r'\s+', ' ', summary.strip())
86
 
87
- return summary
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
- def summarize_text(self, text, max_length=150, min_length=50):
90
- """Main summarization function"""
91
- if not text or len(text.strip()) < 50:
92
- return "❌ متن ورودی کوتاه است. لطفاً متن طولانی‌تری وارد کنید."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
 
 
 
 
 
 
 
94
  try:
95
- # Preprocess text
96
- clean_text = self.preprocess_persian_text(text)
97
-
98
- # Check text length
99
- if len(clean_text) < 100:
100
- return "❌ متن پس از پردازش کوتاه است. متن بلندتری وارد کنید."
101
-
102
- # Use extractive summarization
103
- summary = self.extractive_summary(clean_text, max_length)
104
-
105
- # Ensure minimum length
106
- if len(summary) < min_length and len(clean_text) > min_length:
107
- # Try with higher target length
108
- summary = self.extractive_summary(clean_text, min(max_length + 50, len(clean_text)))
109
-
110
- return summary if summary else "❌ نتوانستم خلاصه مناسبی تولید کنم."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
  except Exception as e:
113
- return f" خطا در خلاصه‌سازی: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
  # Initialize the summarizer
116
- persian_summarizer = LightweightPersianSummarizer()
117
 
118
  def summarize_persian_text(text, summary_length):
119
  """Main function to handle summarization requests"""
@@ -129,21 +456,21 @@ def summarize_persian_text(text, summary_length):
129
  # Sample Persian texts for demonstration
130
  sample_texts = {
131
  "خبر سیاسی": """
132
- مجلس شورای اسلامی ایران در جلسه علنی روز گذشته لایحه بودجه سال آینده را بررسی کرد. نمایندگان مجلس در این جلسه به بحث و بررسی جزئیات بودجه پرداختند و پیشنهادات مختلفی برای بهبود آن ارائه دادند. وزیر اقتصاد نیز در این جلسه حضور یافت و به سوالات نمایندگان پاسخ داد. بر اساس این لایحه، بودجه عمومی کشور نسبت به سال جاری افزایش قابل توجهی خواهد داشت. همچنین اعتبارات ویژه‌ای برای توسعه زیرساخت‌های کشور در نظر گرفته شده است. نمایندگان بر لزوم شفافیت در هزینه‌کرد بودجه تأکید کردند و خواستار نظارت دقیق‌تر بر اجرای برنامه‌های توسعه‌ای شدند.
133
  """,
134
 
135
  "مقاله علمی": """
136
- هوش مصنوعی در دهه‌های اخیر به یکی از مهم‌ترین فناوری‌های نوین تبدیل شده است. این فناوری کاربردهای گسترده‌ای در زمینه‌های مختلف نظیر پزشکی، آموزش، حمل و نقل و صنعت دارد. یادگیری ماشین که بخش مهمی از هوش مصنوعی محسوب می‌شود، امکان تحلیل داده‌های پیچیده و الگویابی را فراهم می‌کند. شبکه‌های ع��بی مصنوعی نیز با الهام از مغز انسان طراحی شده‌اند و قابلیت‌های شگفت‌انگیزی در تشخیص الگو و پردازش تصویر دارند. با این حال، چالش‌هایی نظیر اخلاق در هوش مصنوعی و حفظ حریم خصوصی همچنان وجود دارد. محققان بر لزوم توسعه هوش مصنوعی مسئولانه تأکید می‌کنند تا از مزایای این فناوری استفاده کرده و از مضرات احتمالی آن جلوگیری شود.
137
  """,
138
 
139
  "متن ادبی": """
140
- در باغ گل‌های سرخ، پیرمردی با موهای سفید نشسته بود و به آسمان آبی نگاه می‌کرد. نسیم ملایم صبحگاهی برگ‌های درختان را به رقص درآورده بود. صدای آب نهری که از دور می‌آمد، آرامش خاصی به فضا می‌بخشید. پیرمرد در دل خود به یاد روزهای جوانی‌اش بود، زمانی که این باغ را با دستان خود کاشته بود. اکنون پس از سال‌ها، میوه زحماتش را می‌دید. گل‌های رنگارنگ، درختان سایه‌دار و آرامش این مکان، همه نشان از عشق و دلبستگی او به این باغ داشت. او لبخندی بر لب داشت، لبخندی که حاکی از رضایت و آرامش درونی بود. این باغ نه تنها مکانی برای استراحت، بلکه خانه‌ای برای خاطرات شیرین او بود.
141
  """
142
  }
143
 
144
  # Create Gradio interface
145
  with gr.Blocks(
146
- title="خلاصه‌ساز متن فارسی - نسخه سبک",
147
  theme=gr.themes.Soft(),
148
  css="""
149
  .persian-text {
@@ -162,8 +489,9 @@ with gr.Blocks(
162
  gr.HTML("""
163
  <div class="main-header">
164
  <h1>🤖 خلاصه‌ساز هوشمند متن فارسی</h1>
165
- <p><strong>Persian Text Summarization Tool - Lightweight Version</strong></p>
166
- <p>این ابزار با روش استخراجی پیشرفته، متن‌های فارسی را خلاصه می‌کند</p>
 
167
  </div>
168
  """)
169
 
@@ -171,14 +499,43 @@ with gr.Blocks(
171
  with gr.Column(scale=2):
172
  gr.Markdown("## 📝 متن ورودی")
173
 
174
- # Method info
175
- gr.HTML("""
176
- <div style="padding: 10px; background-color: #e3f2fd; border-radius: 5px; margin-bottom: 10px;">
177
- <strong> روش:</strong> خلاصه‌سازی استخراجی پیشرفته<br>
178
- <strong> مزیت:</strong> سریع، پایدار و کاملاً فارسی<br>
179
- <strong>🎯 کیفیت:</strong> بالا برای متن‌های فارسی
180
- </div>
181
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
 
183
  # Sample text selector
184
  sample_selector = gr.Dropdown(
@@ -228,23 +585,55 @@ with gr.Blocks(
228
  gr.Markdown("""
229
  ### نحوه استفاده:
230
  1. **متن خود را وارد کنید**: متن فارسی خود را در قسمت "متن ورودی" بنویسید یا کپی کنید
231
- 2. **نمونه متن انتخاب کنید**: میتوانید از نمونه متن‌های آماده استفاده کنید
232
- 3. **طول خلاصه را تعیین کنید**: کوتاه، متوسط یا بلند
233
- 4. **دکمه خلاصه‌سازی را بزنید**: منتظر بمانید تا خلاصه تولید شود
234
-
235
- ### ویژگی‌های نسخه سبک:
236
- - سریع و پایدار (بدون نیاز به مدل‌های سنگین)
237
- - ✅ کاملاً فارسی (هیچ ترجمه‌ای انجام نمی‌شود)
238
- - ✅ کیفیت بالا برای متن‌های فارسی
239
- - ✅ مناسب برای همه سرورها
240
- - ✅ روش استخراجی هوشمند
241
- - ✅ امتیازدهی پیشرفته به جملات
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
 
243
  ### نکات:
244
  - متن ورودی باید حداقل 100 کاراکتر باشد
245
- - جملات ابتدایی و میانی متن امتیاز بیشتری دریافت می‌کنند
246
- - این نسخه برای استقرار آسان طراحی شده است
247
- - هیچ وابستگی پیچیده‌ای ندارد
 
 
 
248
  """)
249
 
250
  # Event handlers
@@ -270,6 +659,21 @@ with gr.Blocks(
270
 
271
  return summary, orig_len, summ_len, ratio
272
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  # Connect events
274
  sample_selector.change(
275
  fn=load_sample_text,
@@ -277,6 +681,24 @@ with gr.Blocks(
277
  outputs=[text_input]
278
  )
279
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  summarize_btn.click(
281
  fn=process_summarization,
282
  inputs=[text_input, summary_length],
@@ -284,8 +706,8 @@ with gr.Blocks(
284
  )
285
 
286
  clear_btn.click(
287
- fn=lambda: ("", "", 0, 0, 0),
288
- outputs=[text_input, summary_output, original_length, summary_length_num, compression_ratio]
289
  )
290
 
291
  # Launch the app
 
1
  import gradio as gr
2
+ import torch
3
+ from transformers import AutoTokenizer, AutoModelForSeq2SeqLM, pipeline
4
  import re
5
  import warnings
6
+ import io
7
+ import requests
8
+ from bs4 import BeautifulSoup
9
+ from urllib.parse import urlparse, parse_qs
10
  warnings.filterwarnings("ignore")
11
 
12
+ class PersianSummarizer:
13
  def __init__(self):
14
+ # Using mT5 which is more stable and supports Persian well
15
+ self.model_name = "google/mt5-small"
16
+ self.tokenizer = None
17
+ self.model = None
18
+ self.load_model()
19
+
20
+ def load_model(self):
21
+ """Load the multilingual T5 model for Persian summarization"""
22
+ try:
23
+ print("Loading mT5 model for Persian summarization...")
24
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
25
+ self.model = AutoModelForSeq2SeqLM.from_pretrained(self.model_name)
26
+ print("mT5 model loaded successfully!")
27
+ except Exception as e:
28
+ print(f"Error loading model: {e}")
29
+ # Fallback to pipeline approach
30
+ try:
31
+ from transformers import pipeline
32
+ self.pipeline = pipeline("summarization", model="facebook/bart-large-cnn")
33
+ self.model = None
34
+ print("Fallback pipeline loaded!")
35
+ except Exception as e2:
36
+ print(f"All models failed: {e2}")
37
+ self.model = None
38
+ self.pipeline = None
39
 
40
  def preprocess_persian_text(self, text):
41
  """Clean and preprocess Persian text"""
 
47
 
48
  return text
49
 
50
+ def summarize_text(self, text, max_length=150, min_length=50):
51
+ """Summarize Persian text keeping output in Persian"""
52
+ if not text or len(text.strip()) < 50:
53
+ return "❌ متن ورودی کوتاه است. لطفاً متن طولانی‌تری وارد کنید."
54
+
55
+ try:
56
+ # Preprocess text
57
+ clean_text = self.preprocess_persian_text(text)
58
+
59
+ # Check text length
60
+ if len(clean_text) < 100:
61
+ return "❌ متن پس از پردازش کوتاه است. متن بلندتری وارد کنید."
62
+
63
+ if self.model is None:
64
+ # Use fallback methods
65
+ if hasattr(self, 'pipeline') and self.pipeline:
66
+ # Try with pipeline but post-process to stay Persian
67
+ result = self.pipeline(clean_text, max_length=max_length, min_length=min_length)
68
+ return self.ensure_persian_output(result[0]['summary_text'], clean_text)
69
+ else:
70
+ # Pure extractive method
71
+ return self.extractive_summary(clean_text, max_length)
72
+
73
+ # Use mT5 with Persian-specific prompting
74
+ # Add Persian instruction to encourage Persian output
75
+ persian_prompt = f"خلاصه کنید: {clean_text}"
76
+
77
+ # Tokenize with proper settings
78
+ inputs = self.tokenizer(
79
+ persian_prompt,
80
+ max_length=512, # Reduced for stability
81
+ padding=True,
82
+ truncation=True,
83
+ return_tensors="pt"
84
+ )
85
+
86
+ # Generate summary
87
+ with torch.no_grad():
88
+ summary_ids = self.model.generate(
89
+ inputs["input_ids"],
90
+ attention_mask=inputs.get("attention_mask"),
91
+ max_length=max_length,
92
+ min_length=min_length,
93
+ num_beams=2, # Reduced for speed
94
+ length_penalty=1.0,
95
+ repetition_penalty=1.1,
96
+ early_stopping=True,
97
+ do_sample=False
98
+ )
99
+
100
+ # Decode and clean
101
+ summary = self.tokenizer.decode(summary_ids[0], skip_special_tokens=True)
102
+
103
+ # Clean up the summary and ensure it's Persian
104
+ summary = self.clean_persian_summary(summary, clean_text)
105
+
106
+ return summary
107
+
108
+ # File processing functions
109
+ def extract_text_from_file(file_path):
110
+ """Extract text from uploaded file"""
111
+ if file_path is None:
112
+ return None, "❌ فایلی انتخاب نشده است."
113
+
114
+ try:
115
+ file_extension = file_path.name.lower().split('.')[-1]
116
 
117
+ if file_extension == 'txt':
118
+ # Read plain text file
119
+ with open(file_path.name, 'r', encoding='utf-8') as f:
120
+ content = f.read()
121
+ return content, f"✅ فایل متنی با موفقیت خوانده شد. ({len(content)} کاراکتر)"
122
 
123
+ elif file_extension == 'docx':
124
+ # Try to read Word document
125
+ try:
126
+ import docx
127
+ doc = docx.Document(file_path.name)
128
+ content = '\n'.join([paragraph.text for paragraph in doc.paragraphs])
129
+ return content, f"✅ فایل Word با موفقیت خوانده شد. ({len(content)} کاراکتر)"
130
+ except ImportError:
131
+ return None, "❌ برای خواندن فایل Word نیاز به نصب python-docx دارید."
132
 
133
+ elif file_extension == 'pdf':
134
+ # Try to read PDF
135
+ try:
136
+ import PyPDF2
137
+ with open(file_path.name, 'rb') as file:
138
+ pdf_reader = PyPDF2.PdfReader(file)
139
+ content = ''
140
+ for page in pdf_reader.pages:
141
+ content += page.extract_text() + '\n'
142
+ return content, f"✅ فایل PDF با موفقیت خوانده شد. ({len(content)} کاراکتر)"
143
+ except ImportError:
144
+ return None, "❌ برای خواندن فایل PDF نیاز به نصب PyPDF2 دارید."
145
+
146
+ else:
147
+ # Try to read as plain text anyway
148
+ try:
149
+ with open(file_path.name, 'r', encoding='utf-8') as f:
150
+ content = f.read()
151
+ return content, f"✅ فایل به عنوان متن خوانده شد. ({len(content)} کاراکتر)"
152
+ except UnicodeDecodeError:
153
+ try:
154
+ with open(file_path.name, 'r', encoding='cp1256') as f:
155
+ content = f.read()
156
+ return content, f"✅ فایل با کدگذاری cp1256 خوانده شد. ({len(content)} کاراکتر)"
157
+ except:
158
+ return None, f"❌ قادر به خواندن فایل {file_extension} نیستم. فرمت‌های پشتیبانی شده: txt, docx, pdf"
159
 
160
+ except Exception as e:
161
+ return None, f" خطا در خواندن فایل: {str(e)}"
162
+
163
+ def process_uploaded_file(file):
164
+ """Process uploaded file and return text content"""
165
+ if file is None:
166
+ return "", "📁 فایل خود را انتخاب کنید"
167
+
168
+ content, message = extract_text_from_file(file)
169
+
170
+ if content:
171
+ # Clean the content
172
+ content = persian_summarizer.preprocess_persian_text(content)
173
+ return content, message
174
+ else:
175
+ return "", message
176
+
177
+ # URL processing functions
178
+ def extract_text_from_url(url):
179
+ """Extract text content from a URL"""
180
+ if not url or not url.strip():
181
+ return None, "❌ لطفاً آدرس وب‌سایت را وارد کنید."
182
+
183
+ # Add protocol if missing
184
+ if not url.startswith(('http://', 'https://')):
185
+ url = 'https://' + url
186
+
187
+ # Validate URL format
188
+ try:
189
+ parsed = urlparse(url)
190
+ if not parsed.netloc:
191
+ return None, "❌ آدرس وب‌سایت معتبر نیست."
192
+ except:
193
+ return None, "❌ آدرس وب‌سایت معتبر نیست."
194
+
195
+ try:
196
+ # Set headers to mimic a real browser
197
+ headers = {
198
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
199
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
200
+ 'Accept-Language': 'fa,en-US,en;q=0.5',
201
+ 'Accept-Encoding': 'gzip, deflate',
202
+ 'Connection': 'keep-alive',
203
+ }
204
 
205
+ # Fetch the webpage
206
+ response = requests.get(url, headers=headers, timeout=10, verify=False)
207
+ response.raise_for_status()
208
 
209
+ # Parse HTML content
210
+ soup = BeautifulSoup(response.content, 'html.parser')
 
 
 
211
 
212
+ # Remove script and style elements
213
+ for script in soup(["script", "style", "nav", "footer", "header", "aside"]):
214
+ script.decompose()
215
 
216
+ # Extract text from common content areas
217
+ content_selectors = [
218
+ 'article', 'main', '.content', '.post', '.article',
219
+ '.entry-content', '.post-content', '.article-content',
220
+ '.story-body', '.article-body', '.content-body'
221
+ ]
222
 
223
+ extracted_text = ""
224
+
225
+ # Try to find content in specific areas first
226
+ for selector in content_selectors:
227
+ content_area = soup.select_one(selector)
228
+ if content_area:
229
+ extracted_text = content_area.get_text(separator=' ', strip=True)
 
 
230
  break
231
 
232
+ # If no specific content area found, extract from body
233
+ if not extracted_text:
234
+ body = soup.find('body')
235
+ if body:
236
+ extracted_text = body.get_text(separator=' ', strip=True)
237
+ else:
238
+ extracted_text = soup.get_text(separator=' ', strip=True)
239
 
240
+ # Clean up the text
241
+ extracted_text = re.sub(r'\s+', ' ', extracted_text.strip())
242
 
243
+ if len(extracted_text) < 100:
244
+ return None, "❌ محتوای متنی کافی در این صفحه یافت نشد."
245
 
246
+ return extracted_text, f"✅ محتوای وب‌سایت با موفقیت استخراج شد. ({len(extracted_text)} کاراکتر)"
247
+
248
+ except requests.exceptions.Timeout:
249
+ return None, "❌ زمان اتصال به وب‌سایت تمام شد. لطفاً دوباره تلاش کنید."
250
+ except requests.exceptions.ConnectionError:
251
+ return None, "❌ خطا در اتصال به وب‌سایت. لطفاً اتصال اینترنت خود را بررسی کنید."
252
+ except requests.exceptions.HTTPError as e:
253
+ return None, f"❌ خطا در دریافت صفحه: {e.response.status_code}"
254
+ except Exception as e:
255
+ return None, f"❌ خطا در پردازش وب‌سایت: {str(e)}"
256
+
257
+ def process_url_input(url):
258
+ """Process URL input and return text content"""
259
+ if not url or not url.strip():
260
+ return "", "🌐 آدرس وب‌سایت خود را وارد کنید"
261
 
262
+ content, message = extract_text_from_url(url.strip())
263
+
264
+ if content:
265
+ # Clean the content
266
+ content = persian_summarizer.preprocess_persian_text(content)
267
+ return content, message
268
+ else:
269
+ return "", message
270
+
271
+ # YouTube processing functions
272
+ def extract_youtube_video_id(url):
273
+ """Extract YouTube video ID from URL"""
274
+ if not url:
275
+ return None
276
+
277
+ # Handle different YouTube URL formats
278
+ patterns = [
279
+ r'(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)',
280
+ r'youtube\.com\/watch\?.*v=([^&\n?#]+)'
281
+ ]
282
+
283
+ for pattern in patterns:
284
+ match = re.search(pattern, url)
285
+ if match:
286
+ return match.group(1)
287
+
288
+ return None
289
+
290
+ def extract_youtube_transcript(url):
291
+ """Extract transcript from YouTube video"""
292
+ if not url or not url.strip():
293
+ return None, "❌ لطفاً لینک یوتیوب را وارد کنید."
294
+
295
+ try:
296
+ # Import youtube-transcript-api
297
+ from youtube_transcript_api import YouTubeTranscriptApi
298
+ from youtube_transcript_api._errors import TranscriptsDisabled, NoTranscriptFound
299
+
300
+ # Extract video ID
301
+ video_id = extract_youtube_video_id(url.strip())
302
+ if not video_id:
303
+ return None, "❌ لینک یوتیوب معتبر نیست. مثال: https://youtube.com/watch?v=VIDEO_ID"
304
 
305
+ # Try to get transcript in Persian first, then English
306
+ transcript_list = YouTubeTranscriptApi.list_transcripts(video_id)
307
+
308
+ transcript_text = ""
309
+ language_used = ""
310
+
311
+ # Try Persian first
312
  try:
313
+ transcript = transcript_list.find_transcript(['fa', 'per'])
314
+ transcript_data = transcript.fetch()
315
+ language_used = "فارسی"
316
+ except:
317
+ # Try English
318
+ try:
319
+ transcript = transcript_list.find_transcript(['en'])
320
+ transcript_data = transcript.fetch()
321
+ language_used = "انگلیسی"
322
+ except:
323
+ # Try any available transcript
324
+ try:
325
+ transcript = transcript_list.find_manually_created_transcript(['fa', 'per', 'en'])
326
+ transcript_data = transcript.fetch()
327
+ language_used = "دستی"
328
+ except:
329
+ # Try auto-generated
330
+ try:
331
+ transcript = transcript_list.find_generated_transcript(['fa', 'per', 'en'])
332
+ transcript_data = transcript.fetch()
333
+ language_used = "خودکار"
334
+ except:
335
+ return None, "❌ این ویدیو زیرنویس قابل دسترس ندارد."
336
+
337
+ # Combine transcript text
338
+ transcript_text = ' '.join([entry['text'] for entry in transcript_data])
339
+
340
+ if len(transcript_text) < 100:
341
+ return None, "❌ متن زیرنویس کوتاه است یا کیفیت مناسبی ندارد."
342
+
343
+ return transcript_text, f"✅ زیرنویس یوتیوب استخراج شد ({language_used}). ({len(transcript_text)} کاراکتر)"
344
+
345
+ except ImportError:
346
+ return None, "❌ برای استخراج زیرنویس یوتیوب نیاز به نصب youtube-transcript-api دارید."
347
+ except TranscriptsDisabled:
348
+ return None, "❌ زیرنویس برای این ویدیو غیرفعال است."
349
+ except NoTranscriptFound:
350
+ return None, "❌ هیچ زیرنویسی برای این ویدیو یافت نشد."
351
+ except Exception as e:
352
+ return None, f"❌ خطا در استخراج زیرنویس: {str(e)}"
353
+
354
+ def process_youtube_input(url):
355
+ """Process YouTube URL and return transcript content"""
356
+ if not url or not url.strip():
357
+ return "", "📺 لینک یوتیوب خود را وارد کنید"
358
+
359
+ content, message = extract_youtube_transcript(url.strip())
360
+
361
+ if content:
362
+ # Clean the content
363
+ content = persian_summarizer.preprocess_persian_text(content)
364
+ return content, message
365
+ else:
366
+ return "", message if summary else "❌ نتوانستم خلاصه مناسبی تولید کنم. لطفاً متن دیگری امتحان کنید."
367
 
368
  except Exception as e:
369
+ print(f"Error in summarization: {e}")
370
+ # Always fallback to extractive
371
+ return self.extractive_summary(clean_text, max_length)
372
+
373
+ def ensure_persian_output(self, text, original_text):
374
+ """Ensure output stays in Persian"""
375
+ # If the output seems to be English, use extractive method instead
376
+ english_ratio = len(re.findall(r'[a-zA-Z]', text)) / max(len(text), 1)
377
+ if english_ratio > 0.3: # If more than 30% English characters
378
+ return self.extractive_summary(original_text, 150)
379
+ return text
380
+
381
+ def clean_persian_summary(self, summary, original_text):
382
+ """Clean and ensure Persian summary quality"""
383
+ # Remove the prompt prefix if it exists
384
+ summary = re.sub(r'^خلاصه کنید:?\s*', '', summary)
385
+
386
+ # Remove excessive English characters
387
+ summary = re.sub(r'[a-zA-Z]{3,}', '', summary)
388
+
389
+ # Check if summary is mostly Persian
390
+ persian_chars = len(re.findall(r'[\u0600-\u06FF]', summary))
391
+ total_chars = len(re.sub(r'\s', '', summary))
392
+
393
+ if total_chars == 0 or persian_chars / total_chars < 0.5:
394
+ # If not enough Persian content, use extractive method
395
+ return self.extractive_summary(original_text, 150)
396
+
397
+ # Clean whitespace
398
+ summary = re.sub(r'\s+', ' ', summary.strip())
399
+
400
+ return summary
401
+
402
+ def extractive_summary(self, text, max_length):
403
+ """Simple extractive summarization fallback that keeps Persian"""
404
+ sentences = re.split(r'[.!?؟]', text)
405
+ sentences = [s.strip() for s in sentences if len(s.strip()) > 20]
406
+
407
+ if len(sentences) <= 2:
408
+ return text[:max_length] + ("..." if len(text) > max_length else "")
409
+
410
+ # Score sentences by position and length
411
+ scored_sentences = []
412
+ for i, sentence in enumerate(sentences):
413
+ # Give higher scores to earlier sentences and longer sentences
414
+ position_score = 1.0 - (i / len(sentences)) * 0.5
415
+ length_score = min(len(sentence) / 100, 1.0)
416
+ total_score = position_score + length_score
417
+ scored_sentences.append((sentence, total_score))
418
+
419
+ # Sort by score and take top sentences
420
+ scored_sentences.sort(key=lambda x: x[1], reverse=True)
421
+
422
+ # Select sentences until we reach desired length
423
+ summary_sentences = []
424
+ current_length = 0
425
+
426
+ for sentence, score in scored_sentences:
427
+ if current_length + len(sentence) < max_length:
428
+ summary_sentences.append(sentence)
429
+ current_length += len(sentence)
430
+ else:
431
+ break
432
+
433
+ # Maintain original order
434
+ original_order = []
435
+ for sentence in sentences:
436
+ if sentence in summary_sentences:
437
+ original_order.append(sentence)
438
+
439
+ summary = ". ".join(original_order)
440
+ return summary[:max_length] + ("..." if len(summary) > max_length else "")
441
 
442
  # Initialize the summarizer
443
+ persian_summarizer = PersianSummarizer()
444
 
445
  def summarize_persian_text(text, summary_length):
446
  """Main function to handle summarization requests"""
 
456
  # Sample Persian texts for demonstration
457
  sample_texts = {
458
  "خبر سیاسی": """
459
+ مجلس شورای اسلامی ایران در جلسه علنی روز گذشته لایحه بودجه سال آینده را بررسی کرد. نمایندگان مجلس در این جلسه به بحث و بررسی جزئیات بودجه پرداختند و پیشنهادات مختلفی برای بهبود آن ارائه دادند. وزیر اقتصاد نیز در این جلسه حضور یافت و به سوالات نمایندگان پاسخ داد. بر اساس این لایحه، بودجه عمومی کشور نسبت به سال جاری افزایش قابل توجهی خواهد داشت. همچنین اعتبارات ویژه‌ای برای توسعه زیرساخت‌های کشور در نظر گرفته شده است.
460
  """,
461
 
462
  "مقاله علمی": """
463
+ هوش مصنوعی در دهه‌های اخیر به یکی از مهم‌ترین فناوری‌های نوین تبدیل شده است. این فناوری کاربردهای گسترده‌ای در زمینه‌های مختلف نظیر پزشکی، آموزش، حمل و نقل و صنعت دارد. یادگیری ماشین که بخش مهمی از هوش مصنوعی محسوب می‌شود، امکان تحلیل داده‌های پیچیده و الگویابی را فراهم می‌کند. شبکه‌های عصبی مصنوعی نیز با الهام از مغز انسان طراحی شده‌اند و قابلیت‌های شگفت‌انگیزی در تشخیص الگو و پردازش تصویر دارند. با این حال، چالش‌هایی نظیر اخلاق در هوش مصنوعی و حفظ حریم خصوصی همچنان وجود دارد.
464
  """,
465
 
466
  "متن ادبی": """
467
+ در باغ گل‌های سرخ، پیرمردی با موهای سفید نشسته بود و به آسمان آبی نگاه می‌کرد. نسیم ملایم صبحگاهی برگ‌های درختان را به رقص درآورده بود. صدای آب نهری که از دور می‌آمد، آرامش خاصی به فضا می‌بخشید. پیرمرد در دل خود به یاد روزهای جوانی‌اش بود، زمانی که این باغ را با دستان خود کاشته بود. اکنون پس از سال‌ها، میوه زحماتش را می‌دید. گل‌های رنگارنگ، درختان سایه‌دار و آرامش این مکان، همه نشان از عشق و دلبستگی او به این باغ داشت.
468
  """
469
  }
470
 
471
  # Create Gradio interface
472
  with gr.Blocks(
473
+ title="خلاصه‌ساز متن فارسی",
474
  theme=gr.themes.Soft(),
475
  css="""
476
  .persian-text {
 
489
  gr.HTML("""
490
  <div class="main-header">
491
  <h1>🤖 خلاصه‌ساز هوشمند متن فارسی</h1>
492
+ <p><strong>Persian Text Summarization Tool</strong></p>
493
+ <p>این ابزار با استفاده از هوش مصنوعی، متن‌ها، فایل‌ها، وب‌سایت‌ها و ویدیوهای یوتیوب را خلاصه می‌کند</p>
494
+ <p>📁 فایل‌ها | 🌐 وب‌سایت‌ها | 📺 یوتیوب (زیرنویس)</p>
495
  </div>
496
  """)
497
 
 
499
  with gr.Column(scale=2):
500
  gr.Markdown("## 📝 متن ورودی")
501
 
502
+ # Model status
503
+ model_status = gr.HTML(
504
+ f"""<div style="padding: 10px; background-color: #e8f5e8; border-radius: 5px; margin-bottom: 10px;">
505
+ <strong>🤖 مدل فعال:</strong> mT5-small (پشتیبانی از فارسی)<br>
506
+ <strong>📊 وضعیت:</strong> {'آماده برای خلاصه‌سازی فارسی' if persian_summarizer.model else 'حالت استخراجی (fallback)'}
507
+ </div>"""
508
+ )
509
+
510
+ # File upload section
511
+ with gr.Accordion("📁 آپلود فایل متنی", open=False):
512
+ file_upload = gr.File(
513
+ label="فایل متنی خود را انتخاب کنید",
514
+ file_types=[".txt", ".docx", ".pdf"],
515
+ type="filepath"
516
+ )
517
+ file_status = gr.HTML("📋 فرمت‌های پشتیبانی شده: TXT, DOCX, PDF")
518
+ load_file_btn = gr.Button("📂 بارگذاری متن از فایل", variant="secondary")
519
+
520
+ # URL input section
521
+ with gr.Accordion("🌐 استخراج از وب‌سایت", open=False):
522
+ url_input = gr.Textbox(
523
+ label="آدرس وب‌سایت (URL)",
524
+ placeholder="https://example.com یا example.com",
525
+ lines=1
526
+ )
527
+ url_status = gr.HTML("🌍 وب‌سایت‌های فارسی و انگلیسی پشتیبانی می‌شوند")
528
+ load_url_btn = gr.Button("🔗 استخراج متن از وب‌سایت", variant="secondary")
529
+
530
+ # YouTube input section
531
+ with gr.Accordion("📺 خلاصه ویدیو یوتیوب", open=False):
532
+ youtube_input = gr.Textbox(
533
+ label="لینک یوتیوب",
534
+ placeholder="https://youtube.com/watch?v=... یا https://youtu.be/...",
535
+ lines=1
536
+ )
537
+ youtube_status = gr.HTML("🎬 زیرنویس فارسی و انگلیسی پشتیبانی می‌شود")
538
+ load_youtube_btn = gr.Button("📺 استخراج زیرنویس از یوتیوب", variant="secondary")
539
 
540
  # Sample text selector
541
  sample_selector = gr.Dropdown(
 
585
  gr.Markdown("""
586
  ### نحوه استفاده:
587
  1. **متن خود را وارد کنید**: متن فارسی خود را در قسمت "متن ورودی" بنویسید یا کپی کنید
588
+ 2. **یا فایل آپلود کنید**: فایل متنی (.txt, .docx, .pdf) خود را آپلود کنید
589
+ 3. **یا از وب‌سایت استخراج کنید**: آدرس وب‌سایت مورد نظر را وارد کنید
590
+ 4. **یا ویدیو یوتیوب**: لینک یوتیوب برای استخراج زیرنویس وارد کنید
591
+ 5. **نمونه متن انتخاب کنید**: می‌توانید از نمونه متن‌های آماده استفاده کنید
592
+ 6. **طول خلاصه را تعیین کنید**: کوتاه، متوسط یا بلند
593
+ 7. **دکمه خلاصه‌سازی را بزنید**: منتظر بمانید تا خلاصه تولید شود
594
+
595
+ ### ویژگی‌ها:
596
+ - ✅ پشتیبانی کامل از زبان فارسی با mT5
597
+ - ✅ خروجی کاملاً فارسی (بدون ترجمه به انگلیسی)
598
+ - ✅ آپلود فایل‌های متنی (TXT, DOCX, PDF)
599
+ - ✅ استخراج محتوا از وب‌سایت‌ها
600
+ - ✅ خلاصه ویدیوهای یوتیوب (از طریق زیرنویس)
601
+ - ✅ سه سطح طول خلاصه
602
+ - ✅ نمونه متن‌های آماده
603
+ - ✅ نمایش آمار متن
604
+ - ✅ رابط کاربری فارسی
605
+ - ✅ سیستم fallback پیشرفته
606
+
607
+ ### منابع ورودی پشتیبانی شده:
608
+ - **متن مستقیم**: کپی و پیست متن
609
+ - **فایل‌ها**: TXT, DOCX, PDF
610
+ - **وب‌سایت‌ها**: خبرگاری‌ها، وبلاگ‌ها، مقالات آنلاین
611
+ - **یوتیوب**: ویدیوهای دارای زیرنویس فارسی یا انگلیسی
612
+
613
+ ### فرمت‌های فایل:
614
+ - **TXT**: فایل‌های متن ساده (UTF-8, CP1256)
615
+ - **DOCX**: اسناد Microsoft Word
616
+ - **PDF**: اسناد PDF (نیاز به PyPDF2)
617
+
618
+ ### وب‌سایت‌های پشتیبانی شده:
619
+ - **خبرگاری‌ها**: BBC Persian, Radio Farda, ایسنا و...
620
+ - **وبلاگ‌ها**: وردپرس، بلاگر و...
621
+ - **مقالات**: مجلات آنلاین، سایت‌های آموزشی
622
+
623
+ ### یوتیوب:
624
+ - **زیرنویس فارسی**: ویدیوهای دارای زیرنویس فارسی
625
+ - **زیرنویس انگلیسی**: ترجمه خودکار برای محتوای انگلیسی
626
+ - **زیرنویس خودکار**: زیرنویس‌های تولید شده توسط یوتیوب
627
+ - **فرمت‌های لینک**: youtube.com/watch?v=... یا youtu.be/...
628
 
629
  ### نکات:
630
  - متن ورودی باید حداقل 100 کاراکتر باشد
631
+ - کیفیت خلاصه با متن‌های طولانیتر بهتر است
632
+ - از mT5 برای حفظ زبان فارسی استفاده می‌کند
633
+ - خلاصه تولیدی همیشه به زبان فارسی است
634
+ - برای وب‌سایت‌ها، محتوای اصلی مقاله استخراج می‌شود
635
+ - برای یوتیوب، ابتدا زیرنویس فارسی، سپس انگلیسی جستجو می‌شود
636
+ - فایل‌ها، وب‌سایت‌ها و ویدیوهای بزرگ زمان بیشتری نیاز دارند
637
  """)
638
 
639
  # Event handlers
 
659
 
660
  return summary, orig_len, summ_len, ratio
661
 
662
+ def handle_file_upload(file):
663
+ """Handle file upload and update text input"""
664
+ content, message = process_uploaded_file(file)
665
+ return content, message
666
+
667
+ def handle_url_input(url):
668
+ """Handle URL input and update text input"""
669
+ content, message = process_url_input(url)
670
+ return content, message
671
+
672
+ def handle_youtube_input(url):
673
+ """Handle YouTube URL input and update text input"""
674
+ content, message = process_youtube_input(url)
675
+ return content, message
676
+
677
  # Connect events
678
  sample_selector.change(
679
  fn=load_sample_text,
 
681
  outputs=[text_input]
682
  )
683
 
684
+ load_file_btn.click(
685
+ fn=handle_file_upload,
686
+ inputs=[file_upload],
687
+ outputs=[text_input, file_status]
688
+ )
689
+
690
+ load_url_btn.click(
691
+ fn=handle_url_input,
692
+ inputs=[url_input],
693
+ outputs=[text_input, url_status]
694
+ )
695
+
696
+ load_youtube_btn.click(
697
+ fn=handle_youtube_input,
698
+ inputs=[youtube_input],
699
+ outputs=[text_input, youtube_status]
700
+ )
701
+
702
  summarize_btn.click(
703
  fn=process_summarization,
704
  inputs=[text_input, summary_length],
 
706
  )
707
 
708
  clear_btn.click(
709
+ fn=lambda: ("", "", "", "", "", 0, 0, 0),
710
+ outputs=[text_input, summary_output, file_status, url_status, youtube_status, original_length, summary_length_num, compression_ratio]
711
  )
712
 
713
  # Launch the app