slslslrhfem commited on
Commit
884ce27
·
1 Parent(s): 2978910

change download mechanism

Browse files
Files changed (1) hide show
  1. app.py +205 -122
app.py CHANGED
@@ -10,6 +10,7 @@ import glob
10
  from pathlib import Path
11
  from huggingface_hub import snapshot_download
12
  import shutil
 
13
 
14
  token = os.getenv("HF_TOKEN")
15
 
@@ -175,9 +176,70 @@ if ml_models_path.exists():
175
  for item in ml_models_path.iterdir():
176
  print(f" {item.name}")
177
 
178
- # Import inference
179
  print("=== IMPORTING INFERENCE ===")
180
- from inference import inference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
  def find_song_file_by_title(song_title):
183
  covers80_path = Path("covers80")
@@ -207,6 +269,35 @@ def find_song_file_by_title(song_title):
207
 
208
  return None
209
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  def format_time(seconds):
211
  """Convert seconds to MM:SS format"""
212
  if seconds is None or seconds < 0:
@@ -219,34 +310,39 @@ def format_time(seconds):
219
  @spaces.GPU(duration=300)
220
  def process_audio_for_matching(audio_file):
221
  if audio_file is None:
222
- return None, None, None, """
223
  <div style='text-align: center; color: #dc2626; padding: 20px; background: #fef2f2; border-radius: 8px;'>
224
  <h3>No Audio File</h3>
225
  <p>Please upload an audio file to get started!</p>
226
  </div>
227
- """
228
 
229
  result = inference(audio_file)
230
 
231
  if result.get('message') != 'success':
232
- return None, None, None, f"""
233
  <div style="text-align: center; padding: 20px; background: #fefce8; border-radius: 8px;">
234
  <h3 style="color: #a16207;">No Matches Found</h3>
235
  <p style="color: #a16207;">{result.get('message', 'Unknown error occurred')}</p>
236
  </div>
237
- """
238
 
239
  matches = result.get('matches', [])
240
  if not matches:
241
- return None, None, None, """
242
  <div style="text-align: center; padding: 20px; background: #fefce8; border-radius: 8px;">
243
  <h3 style="color: #a16207;">No Matches Found</h3>
244
  <p style="color: #a16207;">No matching vocals found in the dataset.</p>
245
  </div>
246
- """
247
 
248
- # Get audio files for top 3 matches
249
- audio_files = [None, None, None]
 
 
 
 
 
250
  for i, match in enumerate(matches[:3]):
251
  song_title = match.get('song_title', 'Unknown Song')
252
  song_file_path = find_song_file_by_title(song_title)
@@ -255,25 +351,36 @@ def process_audio_for_matching(audio_file):
255
  print(f" File path: {song_file_path}")
256
 
257
  if song_file_path and os.path.exists(song_file_path):
258
- audio_files[i] = song_file_path
259
- else:
260
- audio_files[i] = None
 
 
 
 
 
 
 
 
 
 
 
261
 
262
- # Generate match results HTML - 클릭 가능한 timestamp 포함
263
  matches_html = ""
264
  for i, match in enumerate(matches[:3]):
265
  rank = match.get('rank', 0)
266
  song_title = match.get('song_title', 'Unknown Song')
267
  confidence = match.get('confidence', '0%')
268
  test_time = match.get('test_time', 0)
 
269
  library_time = match.get('library_time', 0)
 
270
 
271
  # Ranking colors
272
  rank_colors = {1: '#dc2626', 2: '#ea580c', 3: '#16a34a'}
273
  rank_color = rank_colors.get(rank, '#6b7280')
274
 
275
- # 클릭 가능한 timestamp 생성 - 이 변수들은 이제 사용하지 않음
276
-
277
  matches_html += f"""
278
  <div style="background: #ffffff; border-radius: 8px; padding: 15px; margin: 10px 0;
279
  border-left: 4px solid {rank_color}; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
@@ -288,21 +395,15 @@ def process_audio_for_matching(audio_file):
288
  </div>
289
  <div style="display: flex; gap: 15px; align-items: center;">
290
  <div style="text-align: center;">
291
- <small style="color: #6b7280;">Your Audio</small>
292
  <div style="color: #dc2626; font-weight: 600;">
293
- <span class="timestamp-link" onclick="window.seekAudio('input', {test_time if test_time else 0})"
294
- title="Click to play at {format_time(test_time)} in your uploaded audio">
295
- {format_time(test_time)}
296
- </span>
297
  </div>
298
  </div>
299
  <div style="text-align: center;">
300
- <small style="color: #6b7280;">Matched At</small>
301
  <div style="color: #16a34a; font-weight: 600;">
302
- <span class="timestamp-link" onclick="window.seekAudio('match{i+1}', {library_time if library_time else 0})"
303
- title="Click to play at {format_time(library_time)} in matched song">
304
- {format_time(library_time)}
305
- </span>
306
  </div>
307
  </div>
308
  <div style="background: #f3f4f6; color: #111827; padding: 4px 10px; border-radius: 12px; font-weight: 600; font-size: 0.9em;">
@@ -319,15 +420,15 @@ def process_audio_for_matching(audio_file):
319
  <div style="text-align: center; margin-bottom: 20px;">
320
  <h3 style="color: #111827; margin: 0;">Vocal Matching Results</h3>
321
  <p style="color: #6b7280; margin: 5px 0;">Found {len(matches)} similar vocals in Covers80 dataset</p>
322
- <p style="color: #2563eb; margin: 5px 0; font-size: 0.9em;">💡 Click on timestamps to jump to that time in the audio</p>
323
  </div>
324
  {matches_html}
325
  </div>
326
  """
327
 
328
- return audio_files[0], audio_files[1], audio_files[2], results_html
329
 
330
- # CSS styles with timestamp styling and JavaScript
331
  custom_css = """
332
  .gradio-container {
333
  background: #f9fafb !important;
@@ -340,99 +441,27 @@ custom_css = """
340
  box-shadow: 0 4px 20px rgba(0,0,0,0.08) !important;
341
  margin: 0 auto !important;
342
  padding: 30px !important;
343
- max-width: 1200px;
344
  border: 1px solid #e5e7eb !important;
345
  }
346
- .timestamp-link {
347
- cursor: pointer !important;
348
- color: #2563eb !important;
349
- font-weight: 600 !important;
350
- text-decoration: underline !important;
351
- transition: color 0.2s !important;
352
  }
353
- .timestamp-link:hover {
354
- color: #1d4ed8 !important;
355
- background-color: #eff6ff !important;
356
- padding: 2px 4px !important;
357
- border-radius: 4px !important;
358
- }
359
- """
360
-
361
- # JavaScript for timestamp functionality
362
- timestamp_js = """
363
- <script>
364
- function seekToTime(audioType, time) {
365
- console.log('Seeking to time:', time, 'in audio type:', audioType);
366
-
367
- // Get all audio elements on page
368
- const allAudios = document.querySelectorAll('audio');
369
- console.log('Found', allAudios.length, 'audio elements');
370
-
371
- let audioElement = null;
372
-
373
- if (audioType === 'input') {
374
- // First audio is input
375
- audioElement = allAudios[0];
376
- } else if (audioType.startsWith('match')) {
377
- // match1 = allAudios[1], match2 = allAudios[2], match3 = allAudios[3]
378
- const matchNum = parseInt(audioType.replace('match', ''));
379
- audioElement = allAudios[matchNum];
380
- }
381
-
382
- if (audioElement) {
383
- console.log('Found audio element:', audioElement);
384
- console.log('Audio src:', audioElement.src);
385
- console.log('Audio readyState:', audioElement.readyState);
386
- console.log('Audio duration:', audioElement.duration);
387
-
388
- // Just set the currentTime directly
389
- audioElement.currentTime = time;
390
- console.log('Set currentTime to:', time);
391
- console.log('Actual currentTime now:', audioElement.currentTime);
392
-
393
- } else {
394
- console.log('Audio element not found for:', audioType);
395
- console.log('Available audio elements:', allAudios.length);
396
- }
397
  }
398
-
399
- // Make function globally available
400
- window.seekToTime = seekToTime;
401
- </script>
402
  """
403
 
404
- # Gradio interface with horizontal layout
405
- with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="Music Plagiarism Detection", head="""
406
- <script>
407
- // Global function for seeking audio
408
- window.seekAudio = function(audioType, time) {
409
- console.log('Seeking to time:', time, 'in audio type:', audioType);
410
-
411
- setTimeout(() => {
412
- const allAudios = document.querySelectorAll('audio');
413
- console.log('Found', allAudios.length, 'audio elements');
414
-
415
- let audioElement = null;
416
-
417
- if (audioType === 'input') {
418
- audioElement = allAudios[0];
419
- } else if (audioType.startsWith('match')) {
420
- const matchNum = parseInt(audioType.replace('match', ''));
421
- audioElement = allAudios[matchNum];
422
- }
423
-
424
- if (audioElement) {
425
- console.log('Found audio element, setting time to:', time);
426
- audioElement.currentTime = time;
427
- // Try to play
428
- audioElement.play().catch(e => console.log('Play blocked:', e.message));
429
- } else {
430
- console.log('Audio element not found');
431
- }
432
- }, 100);
433
- };
434
- </script>
435
- """) as demo:
436
 
437
  gr.Markdown("""
438
  <div style="text-align: center; margin-bottom: 20px;">
@@ -458,20 +487,74 @@ window.seekAudio = function(audioType, time) {
458
  with gr.Row():
459
  submit_btn = gr.Button("Analyze Audio", variant="primary", size="lg")
460
 
461
- # Output section - horizontal layout
462
  with gr.Row():
463
- with gr.Column(scale=1):
464
- audio1 = gr.Audio(label="Match #1", show_label=True, elem_id="audio_1")
465
- audio2 = gr.Audio(label="Match #2", show_label=True, elem_id="audio_2")
466
- audio3 = gr.Audio(label="Match #3", show_label=True, elem_id="audio_3")
 
 
 
 
 
 
467
 
 
468
  with gr.Column(scale=1):
469
  results = gr.HTML(label="Analysis Results")
470
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
471
  submit_btn.click(
472
  fn=process_audio_for_matching,
473
  inputs=[audio_input],
474
- outputs=[audio1, audio2, audio3, results]
475
  )
476
 
477
  if __name__ == "__main__":
 
10
  from pathlib import Path
11
  from huggingface_hub import snapshot_download
12
  import shutil
13
+ import tempfile
14
 
15
  token = os.getenv("HF_TOKEN")
16
 
 
176
  for item in ml_models_path.iterdir():
177
  print(f" {item.name}")
178
 
179
+ # Import updated inference
180
  print("=== IMPORTING INFERENCE ===")
181
+
182
+ # Updated inference functions
183
+ def inference(audio_path):
184
+ from segment_transcription import segment_transcription
185
+ from compare import get_one_result
186
+
187
+ segment_datas = segment_transcription(audio_path)
188
+ result = get_one_result(segment_datas)
189
+ final_result = result_formatting(result)
190
+ return final_result
191
+
192
+ def result_formatting(result):
193
+ """
194
+ get_one_result에서 나온 결과를 포맷팅
195
+ result: sorted list of CompareHelper objects
196
+ """
197
+ if not result or len(result) == 0:
198
+ return {
199
+ 'matches': [],
200
+ 'message': 'No matches found'
201
+ }
202
+
203
+ # 에러 메시지 체크
204
+ if isinstance(result, list) and len(result) > 0 and isinstance(result[0], str):
205
+ return {
206
+ 'matches': [],
207
+ 'message': result[0] # "there is no note for this song"
208
+ }
209
+
210
+ # 상위 3개 결과 추출
211
+ top_3_results = []
212
+ for i, compare_helper in enumerate(result[:3]):
213
+ score = compare_helper.data[0] # similarity score
214
+ test_label = compare_helper.data[1] # test song info
215
+ library_label = compare_helper.data[2] # matched song info
216
+
217
+ # 라이브러리 레이블에서 정보 추출
218
+ song_title = library_label.get('title', 'Unknown Song')
219
+ library_time = library_label.get('time', 0) # 매치된 구간의 시간
220
+ library_time2 = library_label.get('time2', 0)
221
+
222
+ # 테스트 레이블에서 정보 추출
223
+ test_time = test_label.get('time', 0) if test_label else 0 # 입력 곡의 시간
224
+ test_time2 = test_label.get('time2', 0) if test_label else 0
225
+
226
+ match_info = {
227
+ 'rank': i + 1,
228
+ 'score': float(score),
229
+ 'song_title': song_title,
230
+ 'test_time': float(test_time), # 입력 곡에서 매치된 시간
231
+ 'test_time2' : float(test_time2),
232
+ 'library_time': float(library_time), # 라이브러리 곡에서 매치된 시간
233
+ 'library_time2': float(library_time2),
234
+ 'confidence': f"{score * 100:.1f}%",
235
+ 'time_match': f"Input: {test_time:.1f}s ↔ Library: {library_time:.1f}s"
236
+ }
237
+ top_3_results.append(match_info)
238
+
239
+ return {
240
+ 'matches': top_3_results,
241
+ 'message': 'success'
242
+ }
243
 
244
  def find_song_file_by_title(song_title):
245
  covers80_path = Path("covers80")
 
269
 
270
  return None
271
 
272
+ def extract_audio_segment(audio_file_path, start_time, end_time):
273
+ """
274
+ 오디오 파일에서 특정 구간을 추출하여 임시 파일로 저장
275
+ """
276
+ try:
277
+ # Load audio file
278
+ y, sr = librosa.load(audio_file_path, sr=None)
279
+
280
+ # Convert time to samples
281
+ start_sample = int(start_time * sr)
282
+ end_sample = int(end_time * sr)
283
+
284
+ # Extract segment
285
+ segment = y[start_sample:end_sample]
286
+
287
+ # Create temporary file
288
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.wav')
289
+ temp_file.close()
290
+
291
+ # Save segment
292
+ import soundfile as sf
293
+ sf.write(temp_file.name, segment, sr)
294
+
295
+ return temp_file.name
296
+
297
+ except Exception as e:
298
+ print(f"Error extracting segment: {e}")
299
+ return None
300
+
301
  def format_time(seconds):
302
  """Convert seconds to MM:SS format"""
303
  if seconds is None or seconds < 0:
 
310
  @spaces.GPU(duration=300)
311
  def process_audio_for_matching(audio_file):
312
  if audio_file is None:
313
+ return [None] * 10 + ["""
314
  <div style='text-align: center; color: #dc2626; padding: 20px; background: #fef2f2; border-radius: 8px;'>
315
  <h3>No Audio File</h3>
316
  <p>Please upload an audio file to get started!</p>
317
  </div>
318
+ """]
319
 
320
  result = inference(audio_file)
321
 
322
  if result.get('message') != 'success':
323
+ return [None] * 10 + [f"""
324
  <div style="text-align: center; padding: 20px; background: #fefce8; border-radius: 8px;">
325
  <h3 style="color: #a16207;">No Matches Found</h3>
326
  <p style="color: #a16207;">{result.get('message', 'Unknown error occurred')}</p>
327
  </div>
328
+ """]
329
 
330
  matches = result.get('matches', [])
331
  if not matches:
332
+ return [None] * 10 + ["""
333
  <div style="text-align: center; padding: 20px; background: #fefce8; border-radius: 8px;">
334
  <h3 style="color: #a16207;">No Matches Found</h3>
335
  <p style="color: #a16207;">No matching vocals found in the dataset.</p>
336
  </div>
337
+ """]
338
 
339
+ # Initialize audio outputs
340
+ audio_outputs = [None] * 10
341
+
342
+ # Original uploaded audio (index 0)
343
+ audio_outputs[0] = audio_file
344
+
345
+ # Get full songs and segments for top 3 matches
346
  for i, match in enumerate(matches[:3]):
347
  song_title = match.get('song_title', 'Unknown Song')
348
  song_file_path = find_song_file_by_title(song_title)
 
351
  print(f" File path: {song_file_path}")
352
 
353
  if song_file_path and os.path.exists(song_file_path):
354
+ # Full matched song (indices 1, 2, 3)
355
+ audio_outputs[1 + i] = song_file_path
356
+
357
+ # Extract segments for input audio (indices 4, 6, 8)
358
+ input_start = match.get('test_time', 0)
359
+ input_end = match.get('test_time2', input_start + 10) # Default 10 seconds if no end time
360
+ input_segment = extract_audio_segment(audio_file, input_start, input_end)
361
+ audio_outputs[4 + i * 2] = input_segment
362
+
363
+ # Extract segments for matched song (indices 5, 7, 9)
364
+ library_start = match.get('library_time', 0)
365
+ library_end = match.get('library_time2', library_start + 10) # Default 10 seconds if no end time
366
+ library_segment = extract_audio_segment(song_file_path, library_start, library_end)
367
+ audio_outputs[5 + i * 2] = library_segment
368
 
369
+ # Generate results HTML
370
  matches_html = ""
371
  for i, match in enumerate(matches[:3]):
372
  rank = match.get('rank', 0)
373
  song_title = match.get('song_title', 'Unknown Song')
374
  confidence = match.get('confidence', '0%')
375
  test_time = match.get('test_time', 0)
376
+ test_time2 = match.get('test_time2', 0)
377
  library_time = match.get('library_time', 0)
378
+ library_time2 = match.get('library_time2', 0)
379
 
380
  # Ranking colors
381
  rank_colors = {1: '#dc2626', 2: '#ea580c', 3: '#16a34a'}
382
  rank_color = rank_colors.get(rank, '#6b7280')
383
 
 
 
384
  matches_html += f"""
385
  <div style="background: #ffffff; border-radius: 8px; padding: 15px; margin: 10px 0;
386
  border-left: 4px solid {rank_color}; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
 
395
  </div>
396
  <div style="display: flex; gap: 15px; align-items: center;">
397
  <div style="text-align: center;">
398
+ <small style="color: #6b7280;">Your Segment</small>
399
  <div style="color: #dc2626; font-weight: 600;">
400
+ {format_time(test_time)} - {format_time(test_time2)}
 
 
 
401
  </div>
402
  </div>
403
  <div style="text-align: center;">
404
+ <small style="color: #6b7280;">Matched Segment</small>
405
  <div style="color: #16a34a; font-weight: 600;">
406
+ {format_time(library_time)} - {format_time(library_time2)}
 
 
 
407
  </div>
408
  </div>
409
  <div style="background: #f3f4f6; color: #111827; padding: 4px 10px; border-radius: 12px; font-weight: 600; font-size: 0.9em;">
 
420
  <div style="text-align: center; margin-bottom: 20px;">
421
  <h3 style="color: #111827; margin: 0;">Vocal Matching Results</h3>
422
  <p style="color: #6b7280; margin: 5px 0;">Found {len(matches)} similar vocals in Covers80 dataset</p>
423
+ <p style="color: #2563eb; margin: 5px 0; font-size: 0.9em;">🎵 Listen to original songs and extracted segments</p>
424
  </div>
425
  {matches_html}
426
  </div>
427
  """
428
 
429
+ return audio_outputs + [results_html]
430
 
431
+ # CSS styles
432
  custom_css = """
433
  .gradio-container {
434
  background: #f9fafb !important;
 
441
  box-shadow: 0 4px 20px rgba(0,0,0,0.08) !important;
442
  margin: 0 auto !important;
443
  padding: 30px !important;
444
+ max-width: 1400px;
445
  border: 1px solid #e5e7eb !important;
446
  }
447
+ .audio-section {
448
+ background: #f8fafc !important;
449
+ border-radius: 12px !important;
450
+ padding: 15px !important;
451
+ margin: 10px 0 !important;
452
+ border: 1px solid #e2e8f0 !important;
453
  }
454
+ .segment-container {
455
+ background: #fefefe !important;
456
+ border-radius: 8px !important;
457
+ padding: 12px !important;
458
+ border: 1px solid #e5e7eb !important;
459
+ margin: 5px 0 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  }
 
 
 
 
461
  """
462
 
463
+ # Gradio interface
464
+ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="Music Plagiarism Detection") as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
 
466
  gr.Markdown("""
467
  <div style="text-align: center; margin-bottom: 20px;">
 
487
  with gr.Row():
488
  submit_btn = gr.Button("Analyze Audio", variant="primary", size="lg")
489
 
490
+ # Output section
491
  with gr.Row():
492
+ # Left column - Original and Full Songs
493
+ with gr.Column(scale=2):
494
+ gr.Markdown("### 🎵 Original & Matched Songs", elem_classes=["audio-section"])
495
+
496
+ original_audio = gr.Audio(label="Your Uploaded Audio", show_label=True, elem_id="original_audio")
497
+
498
+ with gr.Row():
499
+ match1_full = gr.Audio(label="Match #1 - Full Song", show_label=True, elem_id="match1_full")
500
+ match2_full = gr.Audio(label="Match #2 - Full Song", show_label=True, elem_id="match2_full")
501
+ match3_full = gr.Audio(label="Match #3 - Full Song", show_label=True, elem_id="match3_full")
502
 
503
+ # Right column - Results
504
  with gr.Column(scale=1):
505
  results = gr.HTML(label="Analysis Results")
506
 
507
+ # Segments section
508
+ with gr.Row():
509
+ with gr.Column():
510
+ gr.Markdown("### 🎯 Matched Segments Comparison", elem_classes=["audio-section"])
511
+
512
+ # Match 1 segments
513
+ with gr.Row():
514
+ with gr.Column():
515
+ gr.Markdown("**Match #1 - Your Segment**", elem_classes=["segment-container"])
516
+ match1_input_segment = gr.Audio(label="Your Audio Segment", show_label=False, elem_id="match1_input_seg")
517
+ with gr.Column():
518
+ gr.Markdown("**Match #1 - Matched Segment**", elem_classes=["segment-container"])
519
+ match1_library_segment = gr.Audio(label="Library Segment", show_label=False, elem_id="match1_lib_seg")
520
+
521
+ # Match 2 segments
522
+ with gr.Row():
523
+ with gr.Column():
524
+ gr.Markdown("**Match #2 - Your Segment**", elem_classes=["segment-container"])
525
+ match2_input_segment = gr.Audio(label="Your Audio Segment", show_label=False, elem_id="match2_input_seg")
526
+ with gr.Column():
527
+ gr.Markdown("**Match #2 - Matched Segment**", elem_classes=["segment-container"])
528
+ match2_library_segment = gr.Audio(label="Library Segment", show_label=False, elem_id="match2_lib_seg")
529
+
530
+ # Match 3 segments
531
+ with gr.Row():
532
+ with gr.Column():
533
+ gr.Markdown("**Match #3 - Your Segment**", elem_classes=["segment-container"])
534
+ match3_input_segment = gr.Audio(label="Your Audio Segment", show_label=False, elem_id="match3_input_seg")
535
+ with gr.Column():
536
+ gr.Markdown("**Match #3 - Matched Segment**", elem_classes=["segment-container"])
537
+ match3_library_segment = gr.Audio(label="Library Segment", show_label=False, elem_id="match3_lib_seg")
538
+
539
+ # Define outputs list
540
+ outputs = [
541
+ original_audio, # 0
542
+ match1_full, # 1
543
+ match2_full, # 2
544
+ match3_full, # 3
545
+ match1_input_segment, # 4
546
+ match1_library_segment, # 5
547
+ match2_input_segment, # 6
548
+ match2_library_segment, # 7
549
+ match3_input_segment, # 8
550
+ match3_library_segment, # 9
551
+ results # 10
552
+ ]
553
+
554
  submit_btn.click(
555
  fn=process_audio_for_matching,
556
  inputs=[audio_input],
557
+ outputs=outputs
558
  )
559
 
560
  if __name__ == "__main__":