openfree commited on
Commit
6e94210
Β·
verified Β·
1 Parent(s): ccf27dd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +107 -300
app.py CHANGED
@@ -1345,28 +1345,6 @@ def create_interface():
1345
  padding-bottom: 10px;
1346
  }
1347
 
1348
- .panel-content {
1349
- display: grid;
1350
- grid-template-columns: 1fr 1fr;
1351
- gap: 20px;
1352
- }
1353
-
1354
- .panel-text {
1355
- padding: 15px;
1356
- background: #f9f9f9;
1357
- border-radius: 8px;
1358
- }
1359
-
1360
- .panel-image {
1361
- min-height: 300px;
1362
- background: #f0f0f0;
1363
- border-radius: 8px;
1364
- display: flex;
1365
- align-items: center;
1366
- justify-content: center;
1367
- position: relative;
1368
- }
1369
-
1370
  .episode-structure {
1371
  background: #f5f5f5;
1372
  padding: 15px;
@@ -1375,17 +1353,6 @@ def create_interface():
1375
  max-height: 500px;
1376
  overflow-y: auto;
1377
  }
1378
-
1379
- .edit-field {
1380
- margin: 10px 0;
1381
- }
1382
-
1383
- .edit-label {
1384
- font-weight: bold;
1385
- color: #333;
1386
- margin-bottom: 5px;
1387
- display: block;
1388
- }
1389
  </style>
1390
 
1391
  <div class="main-header">
@@ -1395,7 +1362,7 @@ def create_interface():
1395
  </div>
1396
  """)
1397
 
1398
- # State
1399
  current_session_id = gr.State(None)
1400
  planning_state = gr.State("")
1401
  storyboard_state = gr.State("")
@@ -1451,7 +1418,7 @@ def create_interface():
1451
  value="μž₯λ₯΄λ₯Ό μ„ νƒν•˜κ³  μ½˜μ…‰νŠΈλ₯Ό μž…λ ₯ν•˜μ„Έμš”"
1452
  )
1453
 
1454
- # Planning output - Plain text
1455
  gr.Markdown("### πŸ“– μ›Ήνˆ° κΈ°νšμ•ˆ (40ν™” 전체 ꡬ성)")
1456
  planning_display = gr.Textbox(
1457
  label="κΈ°νšμ•ˆ",
@@ -1461,10 +1428,8 @@ def create_interface():
1461
  value="κΈ°νšμ•ˆμ΄ 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€ (40ν™” 전체 ꡬ성 포함)"
1462
  )
1463
 
1464
- # Character profiles display
1465
  character_display = gr.HTML(label="캐릭터 ν”„λ‘œν•„")
1466
 
1467
- # Download section
1468
  with gr.Row():
1469
  download_planning_btn = gr.Button("πŸ“₯ κΈ°νšμ•ˆ λ‹€μš΄λ‘œλ“œ (40ν™” ꡬ성)", variant="secondary")
1470
 
@@ -1473,103 +1438,40 @@ def create_interface():
1473
  with gr.Tab("🎬 1ν™” μŠ€ν† λ¦¬λ³΄λ“œ (30νŒ¨λ„)"):
1474
  gr.Markdown("""
1475
  ### πŸ“‹ 1ν™” μŠ€ν† λ¦¬λ³΄λ“œ - 30개 νŒ¨λ„
1476
- 각 νŒ¨λ„μ˜ ν…μŠ€νŠΈλ₯Ό 직접 νŽΈμ§‘ν•  수 μžˆμŠ΅λ‹ˆλ‹€. νŽΈμ§‘ ν›„ '변경사항 μ €μž₯' λ²„νŠΌμ„ ν΄λ¦­ν•˜μ„Έμš”.
1477
  """)
1478
 
1479
- # Controls
1480
  with gr.Row():
1481
- save_edits_btn = gr.Button("πŸ’Ύ 변경사항 μ €μž₯", variant="secondary")
1482
- generate_all_images_btn = gr.Button("🎨 λͺ¨λ“  νŒ¨λ„ 이미지 생성 (30개)", variant="primary", size="lg")
1483
  download_storyboard_btn = gr.Button("πŸ“₯ μŠ€ν† λ¦¬λ³΄λ“œ λ‹€μš΄λ‘œλ“œ", variant="secondary")
1484
  clear_images_btn = gr.Button("πŸ—‘οΈ 이미지 μ΄ˆκΈ°ν™”", variant="secondary")
1485
 
1486
  storyboard_download_file = gr.File(visible=False)
 
1487
 
1488
- # Progress indicator
1489
- generation_progress = gr.Textbox(label="생성 μ§„ν–‰ 상황", interactive=False, visible=False)
1490
- edit_status = gr.Textbox(label="νŽΈμ§‘ μƒνƒœ", interactive=False, visible=False)
 
 
 
 
 
1491
 
1492
- # Create panels with editable fields
1493
- panel_components = []
1494
- with gr.Column():
1495
- for i in range(30):
1496
- with gr.Group(visible=False) as panel_group:
1497
- gr.Markdown(f"### 🎬 νŒ¨λ„ {i+1}")
1498
-
1499
- with gr.Row():
1500
- with gr.Column():
1501
- # Editable text fields
1502
- shot_input = gr.Textbox(
1503
- label="μƒ· νƒ€μž…",
1504
- lines=1,
1505
- interactive=True,
1506
- elem_id=f"shot_{i+1}"
1507
- )
1508
-
1509
- prompt_input = gr.Textbox(
1510
- label="이미지 ν”„λ‘¬ν”„νŠΈ (ν•œκΈ€)",
1511
- lines=3,
1512
- interactive=True,
1513
- elem_id=f"prompt_{i+1}"
1514
- )
1515
-
1516
- prompt_en_display = gr.Textbox(
1517
- label="μ˜μ–΄ λ²ˆμ—­ (μžλ™)",
1518
- lines=3,
1519
- interactive=False,
1520
- elem_id=f"prompt_en_{i+1}"
1521
- )
1522
-
1523
- dialogue_input = gr.Textbox(
1524
- label="λŒ€μ‚¬",
1525
- lines=2,
1526
- interactive=True,
1527
- elem_id=f"dialogue_{i+1}"
1528
- )
1529
-
1530
- narration_input = gr.Textbox(
1531
- label="λ‚˜λ ˆμ΄μ…˜",
1532
- lines=2,
1533
- interactive=True,
1534
- elem_id=f"narration_{i+1}"
1535
- )
1536
-
1537
- effects_input = gr.Textbox(
1538
- label="효과음",
1539
- lines=1,
1540
- interactive=True,
1541
- elem_id=f"effects_{i+1}"
1542
- )
1543
-
1544
- with gr.Column():
1545
- # Image display
1546
- panel_image = gr.Image(
1547
- label=f"νŒ¨λ„ {i+1} 이미지",
1548
- elem_id=f"image_{i+1}",
1549
- type="pil"
1550
- )
1551
-
1552
- # Individual regeneration button
1553
- regenerate_btn = gr.Button(
1554
- f"πŸ”„ νŒ¨λ„ {i+1} 이미지 μž¬μƒμ„±",
1555
- variant="secondary",
1556
- elem_id=f"regen_{i+1}"
1557
- )
1558
-
1559
- panel_components.append({
1560
- 'group': panel_group,
1561
- 'shot': shot_input,
1562
- 'prompt': prompt_input,
1563
- 'prompt_en': prompt_en_display,
1564
- 'dialogue': dialogue_input,
1565
- 'narration': narration_input,
1566
- 'effects': effects_input,
1567
- 'image': panel_image,
1568
- 'regenerate': regenerate_btn,
1569
- 'panel_num': i+1
1570
- })
1571
-
1572
- # Event handlers
1573
  def process_query(query, genre, language, session_id):
1574
  system = WebtoonSystem()
1575
  planning = ""
@@ -1583,7 +1485,6 @@ def create_interface():
1583
  yield planning, storyboard, status, new_session_id, character_profiles, system
1584
 
1585
  def format_character_profiles(profiles: Dict[str, CharacterProfile]) -> str:
1586
- """Format character profiles for display"""
1587
  if not profiles:
1588
  return ""
1589
 
@@ -1599,124 +1500,73 @@ def create_interface():
1599
  """
1600
  return html
1601
 
1602
- def update_panel_components(storyboard_content, session_id, character_profiles):
1603
- """Update panel components with parsed storyboard data"""
1604
- if not storyboard_content:
1605
- return [gr.update(visible=False) for _ in range(30*9)], []
1606
-
1607
- if "νŒ¨λ„" not in storyboard_content and "Panel" not in storyboard_content:
1608
- return [gr.update(visible=False) for _ in range(30*9)], []
1609
-
1610
- panel_data = parse_storyboard_panels(storyboard_content, character_profiles)
1611
- updates = []
1612
-
1613
- for i in range(30):
1614
- if i < len(panel_data):
1615
- panel = panel_data[i]
1616
- # Group visibility
1617
- updates.append(gr.update(visible=True))
1618
- # Shot type
1619
- updates.append(gr.update(value=panel.get('shot', '')))
1620
- # Prompt
1621
- updates.append(gr.update(value=panel.get('prompt', '')))
1622
- # Prompt English
1623
- updates.append(gr.update(value=panel.get('prompt_en', '')))
1624
- # Dialogue
1625
- updates.append(gr.update(value=panel.get('dialogue', '')))
1626
- # Narration
1627
- updates.append(gr.update(value=panel.get('narration', '')))
1628
- # Effects
1629
- updates.append(gr.update(value=panel.get('effects', '')))
1630
- # Image (initially empty)
1631
- updates.append(gr.update(value=None))
1632
- # Regenerate button
1633
- updates.append(gr.update())
1634
- else:
1635
- # Hide unused panels
1636
- updates.extend([gr.update(visible=False)] + [gr.update()] * 8)
1637
 
1638
- return updates, panel_data
1639
-
1640
- def save_panel_edits(*args):
1641
- """Save edited panel data"""
1642
- # args contains all panel component values
1643
  panel_data = []
1644
- num_components_per_panel = 8 # Excluding group visibility
 
1645
 
1646
- for i in range(30):
1647
- start_idx = i * num_components_per_panel
1648
- if start_idx < len(args) - 3: # Exclude last 3 state arguments
1649
- panel = {
1650
- 'number': i + 1,
1651
- 'shot': args[start_idx],
1652
- 'prompt': args[start_idx + 1],
1653
- 'prompt_en': args[start_idx + 2],
1654
- 'dialogue': args[start_idx + 3],
1655
- 'narration': args[start_idx + 4],
1656
- 'effects': args[start_idx + 5],
 
 
 
 
 
1657
  'image_url': None
1658
  }
1659
- if panel['prompt']: # Only add panels with content
1660
- panel_data.append(panel)
1661
-
1662
- return panel_data, gr.update(visible=True, value="βœ… 변경사항이 μ €μž₯λ˜μ—ˆμŠ΅λ‹ˆλ‹€")
1663
-
1664
- def generate_single_panel(panel_num, panel_data, session_id, character_profiles, webtoon_system):
1665
- """Generate image for a single panel"""
1666
- if not REPLICATE_API_TOKEN:
1667
- return gr.update(value=None)
1668
-
1669
- if not webtoon_system:
1670
- webtoon_system = WebtoonSystem()
1671
-
1672
- panel = next((p for p in panel_data if p['number'] == panel_num), None)
1673
-
1674
- if not panel or not panel.get('prompt'):
1675
- return gr.update(value=None)
1676
-
1677
- # Translate if needed
1678
- if not panel.get('prompt_en'):
1679
- panel['prompt_en'] = webtoon_system.translate_prompt_to_english(
1680
- panel['prompt'], character_profiles
1681
- )
1682
 
1683
- # Generate image
1684
- result = webtoon_system.image_generator.generate_image(
1685
- panel['prompt_en'],
1686
- f"ep1_panel{panel_num}",
1687
- session_id
1688
- )
1689
-
1690
- if result['status'] == 'success':
1691
- # Load image from URL
1692
- import requests
1693
- from PIL import Image
1694
- from io import BytesIO
1695
-
1696
- response = requests.get(result['image_url'])
1697
- img = Image.open(BytesIO(response.content))
1698
- return gr.update(value=img)
1699
 
1700
- return gr.update(value=None)
1701
 
1702
- def generate_all_images(panel_data, session_id, character_profiles, webtoon_system, progress=gr.Progress()):
1703
- """Generate images for all panels"""
1704
  if not REPLICATE_API_TOKEN:
1705
- return [gr.update()] * 30, gr.update(visible=True, value="⚠️ Replicate API 토큰이 μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€")
 
 
 
1706
 
1707
  if not webtoon_system:
1708
  webtoon_system = WebtoonSystem()
1709
 
1710
- updates = []
1711
  total_panels = len(panel_data)
 
 
1712
 
1713
- for i in range(30):
1714
- if i < total_panels:
1715
- panel = panel_data[i]
1716
- progress((i / total_panels), desc=f"νŒ¨λ„ {i+1}/{total_panels} 생성 쀑...")
1717
-
1718
- if panel.get('prompt'):
1719
- # Translate if needed
1720
  if not panel.get('prompt_en'):
1721
  panel['prompt_en'] = webtoon_system.translate_prompt_to_english(
1722
  panel['prompt'], character_profiles
@@ -1725,39 +1575,38 @@ def create_interface():
1725
  # Generate image
1726
  result = webtoon_system.image_generator.generate_image(
1727
  panel['prompt_en'],
1728
- f"ep1_panel{i+1}",
1729
  session_id
1730
  )
1731
 
1732
  if result['status'] == 'success':
 
1733
  import requests
1734
  from PIL import Image
1735
  from io import BytesIO
1736
 
1737
  response = requests.get(result['image_url'])
1738
  img = Image.open(BytesIO(response.content))
1739
- updates.append(gr.update(value=img))
 
1740
  else:
1741
- updates.append(gr.update(value=None))
1742
- else:
1743
- updates.append(gr.update(value=None))
 
1744
 
1745
  time.sleep(0.5) # Rate limiting
1746
- else:
1747
- updates.append(gr.update())
1748
 
1749
- progress(1.0, desc="이미지 생성 μ™„λ£Œ!")
1750
- return updates, gr.update(visible=False)
1751
 
1752
  def clear_all_images():
1753
- """Clear all images"""
1754
- return [gr.update(value=None) for _ in range(30)]
1755
 
1756
  def handle_random_theme(genre, language):
1757
  return generate_random_webtoon_theme(genre, language)
1758
 
1759
  def download_planning(session_id, planning, genre):
1760
- """Download planning document"""
1761
  try:
1762
  title = f"{genre} μ›Ήνˆ°"
1763
  content = export_planning_to_txt(planning, genre, title)
@@ -1770,23 +1619,9 @@ def create_interface():
1770
  logger.error(f"Download error: {e}")
1771
  return None
1772
 
1773
- def download_storyboard_from_panels(panel_data, genre):
1774
- """Download storyboard from panel data"""
1775
  try:
1776
- content = f"{'=' * 50}\n"
1777
- content += f"{genre} μ›Ήνˆ° - 1ν™” μŠ€ν† λ¦¬λ³΄λ“œ\n"
1778
- content += f"{'=' * 50}\n\n"
1779
-
1780
- for panel in panel_data:
1781
- content += f"\nνŒ¨λ„ {panel['number']}:\n"
1782
- content += f"- μƒ· νƒ€μž…: {panel.get('shot', '')}\n"
1783
- content += f"- 이미지 ν”„λ‘¬ν”„νŠΈ: {panel.get('prompt', '')}\n"
1784
- if panel.get('dialogue'):
1785
- content += f"- λŒ€μ‚¬: {panel.get('dialogue')}\n"
1786
- if panel.get('narration'):
1787
- content += f"- λ‚˜λ ˆμ΄μ…˜: {panel.get('narration')}\n"
1788
- if panel.get('effects'):
1789
- content += f"- 효과음: {panel.get('effects')}\n"
1790
 
1791
  with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8',
1792
  suffix='_storyboard.txt', delete=False) as f:
@@ -1797,8 +1632,6 @@ def create_interface():
1797
  return None
1798
 
1799
  # Connect events
1800
-
1801
- # Main submission
1802
  submit_btn.click(
1803
  fn=process_query,
1804
  inputs=[query_input, genre_select, language_select, current_session_id],
@@ -1812,36 +1645,19 @@ def create_interface():
1812
  inputs=[character_profiles_state],
1813
  outputs=[character_display]
1814
  ).then(
1815
- fn=update_panel_components,
1816
- inputs=[storyboard_state, current_session_id, character_profiles_state],
1817
- outputs=[
1818
- # All panel component updates
1819
- *[comp for panel in panel_components for comp in [
1820
- panel['group'], panel['shot'], panel['prompt'], panel['prompt_en'],
1821
- panel['dialogue'], panel['narration'], panel['effects'],
1822
- panel['image'], panel['regenerate']
1823
- ]],
1824
- panel_data_state
1825
- ]
1826
  )
1827
 
1828
- # Save edits
1829
- all_text_inputs = []
1830
- for panel in panel_components:
1831
- all_text_inputs.extend([
1832
- panel['shot'], panel['prompt'], panel['prompt_en'],
1833
- panel['dialogue'], panel['narration'], panel['effects']
1834
- ])
1835
-
1836
- save_edits_btn.click(
1837
- fn=save_panel_edits,
1838
- inputs=all_text_inputs + [panel_data_state, current_session_id, character_profiles_state],
1839
- outputs=[panel_data_state, edit_status]
1840
  ).then(
1841
- fn=lambda: gr.update(visible=False),
1842
- inputs=[],
1843
- outputs=[edit_status],
1844
- _js="() => setTimeout(() => {}, 3000)"
1845
  )
1846
 
1847
  # Generate all images
@@ -1849,24 +1665,15 @@ def create_interface():
1849
  fn=lambda: gr.update(visible=True, value="이미지 생성 μ‹œμž‘..."),
1850
  outputs=[generation_progress]
1851
  ).then(
1852
- fn=generate_all_images,
1853
  inputs=[panel_data_state, current_session_id, character_profiles_state, webtoon_system],
1854
- outputs=[*[panel['image'] for panel in panel_components], generation_progress]
1855
  )
1856
 
1857
- # Individual panel regeneration
1858
- for panel in panel_components:
1859
- panel['regenerate'].click(
1860
- fn=generate_single_panel,
1861
- inputs=[gr.State(panel['panel_num']), panel_data_state, current_session_id,
1862
- character_profiles_state, webtoon_system],
1863
- outputs=[panel['image']]
1864
- )
1865
-
1866
  # Clear images
1867
  clear_images_btn.click(
1868
  fn=clear_all_images,
1869
- outputs=[panel['image'] for panel in panel_components]
1870
  )
1871
 
1872
  # Random theme
@@ -1888,8 +1695,8 @@ def create_interface():
1888
  )
1889
 
1890
  download_storyboard_btn.click(
1891
- fn=download_storyboard_from_panels,
1892
- inputs=[panel_data_state, genre_select],
1893
  outputs=[storyboard_download_file]
1894
  ).then(
1895
  fn=lambda x: gr.update(visible=True) if x else gr.update(visible=False),
 
1345
  padding-bottom: 10px;
1346
  }
1347
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1348
  .episode-structure {
1349
  background: #f5f5f5;
1350
  padding: 15px;
 
1353
  max-height: 500px;
1354
  overflow-y: auto;
1355
  }
 
 
 
 
 
 
 
 
 
 
 
1356
  </style>
1357
 
1358
  <div class="main-header">
 
1362
  </div>
1363
  """)
1364
 
1365
+ # State variables
1366
  current_session_id = gr.State(None)
1367
  planning_state = gr.State("")
1368
  storyboard_state = gr.State("")
 
1418
  value="μž₯λ₯΄λ₯Ό μ„ νƒν•˜κ³  μ½˜μ…‰νŠΈλ₯Ό μž…λ ₯ν•˜μ„Έμš”"
1419
  )
1420
 
1421
+ # Planning output
1422
  gr.Markdown("### πŸ“– μ›Ήνˆ° κΈ°νšμ•ˆ (40ν™” 전체 ꡬ성)")
1423
  planning_display = gr.Textbox(
1424
  label="κΈ°νšμ•ˆ",
 
1428
  value="κΈ°νšμ•ˆμ΄ 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€ (40ν™” 전체 ꡬ성 포함)"
1429
  )
1430
 
 
1431
  character_display = gr.HTML(label="캐릭터 ν”„λ‘œν•„")
1432
 
 
1433
  with gr.Row():
1434
  download_planning_btn = gr.Button("πŸ“₯ κΈ°νšμ•ˆ λ‹€μš΄λ‘œλ“œ (40ν™” ꡬ성)", variant="secondary")
1435
 
 
1438
  with gr.Tab("🎬 1ν™” μŠ€ν† λ¦¬λ³΄λ“œ (30νŒ¨λ„)"):
1439
  gr.Markdown("""
1440
  ### πŸ“‹ 1ν™” μŠ€ν† λ¦¬λ³΄λ“œ - 30개 νŒ¨λ„
1441
+ 각 νŒ¨λ„μ˜ ν…μŠ€νŠΈλ₯Ό νŽΈμ§‘ν•  수 μžˆμŠ΅λ‹ˆλ‹€. νŽΈμ§‘ ν›„ '적용' λ²„νŠΌμ„ ν΄λ¦­ν•˜μ„Έμš”.
1442
  """)
1443
 
1444
+ # Control buttons
1445
  with gr.Row():
1446
+ apply_edits_btn = gr.Button("βœ… νŽΈμ§‘ λ‚΄μš© 적용", variant="secondary")
1447
+ generate_all_images_btn = gr.Button("🎨 λͺ¨λ“  νŒ¨λ„ 이미지 생성", variant="primary", size="lg")
1448
  download_storyboard_btn = gr.Button("πŸ“₯ μŠ€ν† λ¦¬λ³΄λ“œ λ‹€μš΄λ‘œλ“œ", variant="secondary")
1449
  clear_images_btn = gr.Button("πŸ—‘οΈ 이미지 μ΄ˆκΈ°ν™”", variant="secondary")
1450
 
1451
  storyboard_download_file = gr.File(visible=False)
1452
+ generation_progress = gr.Textbox(label="μ§„ν–‰ 상황", interactive=False, visible=False)
1453
 
1454
+ # Editable storyboard display
1455
+ storyboard_editor = gr.Textbox(
1456
+ label="μŠ€ν† λ¦¬λ³΄λ“œ νŽΈμ§‘κΈ°",
1457
+ lines=30,
1458
+ max_lines=50,
1459
+ interactive=True,
1460
+ placeholder="μŠ€ν† λ¦¬λ³΄λ“œκ°€ μƒμ„±λ˜λ©΄ 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€. 자유둭게 νŽΈμ§‘ν•˜μ„Έμš”."
1461
+ )
1462
 
1463
+ # Panel images gallery
1464
+ gr.Markdown("### πŸ–ΌοΈ μƒμ„±λœ 이미지")
1465
+ images_gallery = gr.Gallery(
1466
+ label="νŒ¨λ„ 이미지",
1467
+ show_label=False,
1468
+ elem_id="gallery",
1469
+ columns=5,
1470
+ rows=6,
1471
+ height="auto"
1472
+ )
1473
+
1474
+ # Helper functions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1475
  def process_query(query, genre, language, session_id):
1476
  system = WebtoonSystem()
1477
  planning = ""
 
1485
  yield planning, storyboard, status, new_session_id, character_profiles, system
1486
 
1487
  def format_character_profiles(profiles: Dict[str, CharacterProfile]) -> str:
 
1488
  if not profiles:
1489
  return ""
1490
 
 
1500
  """
1501
  return html
1502
 
1503
+ def apply_edited_storyboard(edited_text, session_id, character_profiles):
1504
+ """Parse and apply edited storyboard text"""
1505
+ if not edited_text:
1506
+ return [], "μŠ€ν† λ¦¬λ³΄λ“œκ°€ λΉ„μ–΄μžˆμŠ΅λ‹ˆλ‹€."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1507
 
1508
+ # Parse the edited text back into panel data
 
 
 
 
1509
  panel_data = []
1510
+ lines = edited_text.split('\n')
1511
+ current_panel = None
1512
 
1513
+ for line in lines:
1514
+ if 'νŒ¨λ„' in line and any(char.isdigit() for char in line):
1515
+ if current_panel and current_panel.get('prompt'):
1516
+ panel_data.append(current_panel)
1517
+
1518
+ numbers = re.findall(r'\d+', line)
1519
+ panel_num = int(numbers[0]) if numbers else len(panel_data) + 1
1520
+
1521
+ current_panel = {
1522
+ 'number': panel_num,
1523
+ 'shot': '',
1524
+ 'prompt': '',
1525
+ 'prompt_en': '',
1526
+ 'dialogue': '',
1527
+ 'narration': '',
1528
+ 'effects': '',
1529
  'image_url': None
1530
  }
1531
+ elif current_panel:
1532
+ if 'μƒ· νƒ€μž…:' in line or 'Shot type:' in line.lower():
1533
+ current_panel['shot'] = line.split(':', 1)[1].strip() if ':' in line else ''
1534
+ elif '이미지 ν”„λ‘¬ν”„νŠΈ:' in line or 'Image prompt:' in line.lower():
1535
+ current_panel['prompt'] = line.split(':', 1)[1].strip() if ':' in line else ''
1536
+ elif 'λŒ€μ‚¬:' in line or 'Dialogue:' in line.lower():
1537
+ current_panel['dialogue'] = line.split(':', 1)[1].strip() if ':' in line else ''
1538
+ elif 'λ‚˜λ ˆμ΄μ…˜:' in line or 'Narration:' in line.lower():
1539
+ current_panel['narration'] = line.split(':', 1)[1].strip() if ':' in line else ''
1540
+ elif '효과음:' in line or 'Sound effect:' in line.lower():
1541
+ current_panel['effects'] = line.split(':', 1)[1].strip() if ':' in line else ''
 
 
 
 
 
 
 
 
 
 
 
 
1542
 
1543
+ if current_panel and current_panel.get('prompt'):
1544
+ panel_data.append(current_panel)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1545
 
1546
+ return panel_data[:30], f"βœ… {len(panel_data)}개 νŒ¨λ„μ΄ μ μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€."
1547
 
1548
+ def generate_all_panel_images_from_editor(panel_data, session_id, character_profiles, webtoon_system, progress=gr.Progress()):
1549
+ """Generate images for all panels from edited data"""
1550
  if not REPLICATE_API_TOKEN:
1551
+ return [], gr.update(visible=True, value="⚠️ Replicate API 토큰이 μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
1552
+
1553
+ if not panel_data:
1554
+ return [], gr.update(visible=True, value="⚠️ νŒ¨λ„ 데이터가 μ—†μŠ΅λ‹ˆλ‹€.")
1555
 
1556
  if not webtoon_system:
1557
  webtoon_system = WebtoonSystem()
1558
 
1559
+ generated_images = []
1560
  total_panels = len(panel_data)
1561
+ successful = 0
1562
+ failed = 0
1563
 
1564
+ for i, panel in enumerate(panel_data):
1565
+ progress((i / total_panels), desc=f"νŒ¨λ„ {panel['number']}/{total_panels} 생성 쀑...")
1566
+
1567
+ if panel.get('prompt'):
1568
+ try:
1569
+ # Translate to English if needed
 
1570
  if not panel.get('prompt_en'):
1571
  panel['prompt_en'] = webtoon_system.translate_prompt_to_english(
1572
  panel['prompt'], character_profiles
 
1575
  # Generate image
1576
  result = webtoon_system.image_generator.generate_image(
1577
  panel['prompt_en'],
1578
+ f"ep1_panel{panel['number']}",
1579
  session_id
1580
  )
1581
 
1582
  if result['status'] == 'success':
1583
+ # Download and convert image
1584
  import requests
1585
  from PIL import Image
1586
  from io import BytesIO
1587
 
1588
  response = requests.get(result['image_url'])
1589
  img = Image.open(BytesIO(response.content))
1590
+ generated_images.append((img, f"Panel {panel['number']}"))
1591
+ successful += 1
1592
  else:
1593
+ failed += 1
1594
+ except Exception as e:
1595
+ logger.error(f"Error generating panel {panel['number']}: {e}")
1596
+ failed += 1
1597
 
1598
  time.sleep(0.5) # Rate limiting
 
 
1599
 
1600
+ progress(1.0, desc=f"μ™„λ£Œ! 성곡: {successful}, μ‹€νŒ¨: {failed}")
1601
+ return generated_images, gr.update(visible=False)
1602
 
1603
  def clear_all_images():
1604
+ return []
 
1605
 
1606
  def handle_random_theme(genre, language):
1607
  return generate_random_webtoon_theme(genre, language)
1608
 
1609
  def download_planning(session_id, planning, genre):
 
1610
  try:
1611
  title = f"{genre} μ›Ήνˆ°"
1612
  content = export_planning_to_txt(planning, genre, title)
 
1619
  logger.error(f"Download error: {e}")
1620
  return None
1621
 
1622
+ def download_storyboard_from_editor(edited_text, genre):
 
1623
  try:
1624
+ content = export_storyboard_to_txt(edited_text, genre, 1)
 
 
 
 
 
 
 
 
 
 
 
 
 
1625
 
1626
  with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8',
1627
  suffix='_storyboard.txt', delete=False) as f:
 
1632
  return None
1633
 
1634
  # Connect events
 
 
1635
  submit_btn.click(
1636
  fn=process_query,
1637
  inputs=[query_input, genre_select, language_select, current_session_id],
 
1645
  inputs=[character_profiles_state],
1646
  outputs=[character_display]
1647
  ).then(
1648
+ fn=lambda x: x,
1649
+ inputs=[storyboard_state],
1650
+ outputs=[storyboard_editor]
 
 
 
 
 
 
 
 
1651
  )
1652
 
1653
+ # Apply edits button
1654
+ apply_edits_btn.click(
1655
+ fn=apply_edited_storyboard,
1656
+ inputs=[storyboard_editor, current_session_id, character_profiles_state],
1657
+ outputs=[panel_data_state, generation_progress]
 
 
 
 
 
 
 
1658
  ).then(
1659
+ fn=lambda: gr.update(visible=True, value="νŽΈμ§‘ λ‚΄μš©μ΄ μ μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€."),
1660
+ outputs=[generation_progress]
 
 
1661
  )
1662
 
1663
  # Generate all images
 
1665
  fn=lambda: gr.update(visible=True, value="이미지 생성 μ‹œμž‘..."),
1666
  outputs=[generation_progress]
1667
  ).then(
1668
+ fn=generate_all_panel_images_from_editor,
1669
  inputs=[panel_data_state, current_session_id, character_profiles_state, webtoon_system],
1670
+ outputs=[images_gallery, generation_progress]
1671
  )
1672
 
 
 
 
 
 
 
 
 
 
1673
  # Clear images
1674
  clear_images_btn.click(
1675
  fn=clear_all_images,
1676
+ outputs=[images_gallery]
1677
  )
1678
 
1679
  # Random theme
 
1695
  )
1696
 
1697
  download_storyboard_btn.click(
1698
+ fn=download_storyboard_from_editor,
1699
+ inputs=[storyboard_editor, genre_select],
1700
  outputs=[storyboard_download_file]
1701
  ).then(
1702
  fn=lambda x: gr.update(visible=True) if x else gr.update(visible=False),