Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -426,8 +426,8 @@ def add_360_metadata(img):
|
|
| 426 |
|
| 427 |
|
| 428 |
|
| 429 |
-
def create_360_viewer_html(image_paths, output_path):
|
| 430 |
-
"""Create an HTML file with a 360 viewer for the given images."""
|
| 431 |
# Create a list of image data URIs
|
| 432 |
image_data_list = []
|
| 433 |
for img_path in image_paths:
|
|
@@ -435,6 +435,16 @@ def create_360_viewer_html(image_paths, output_path):
|
|
| 435 |
img_data = base64.b64encode(f.read()).decode("utf-8")
|
| 436 |
image_data_list.append(f"data:image/jpeg;base64,{img_data}")
|
| 437 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 438 |
# Create the HTML content
|
| 439 |
html_content = f"""
|
| 440 |
<!DOCTYPE html>
|
|
@@ -442,16 +452,17 @@ def create_360_viewer_html(image_paths, output_path):
|
|
| 442 |
<head>
|
| 443 |
<meta charset="UTF-8">
|
| 444 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 445 |
-
<title>360 Panorama Viewer</title>
|
| 446 |
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pannellum@2.5.6/build/pannellum.css"/>
|
| 447 |
<style>
|
| 448 |
body {{
|
| 449 |
margin: 0;
|
| 450 |
overflow: hidden;
|
|
|
|
| 451 |
}}
|
| 452 |
#panorama {{
|
| 453 |
width: 100vw;
|
| 454 |
-
height:
|
| 455 |
}}
|
| 456 |
.pnlm-hotspot.pnlm-info-hotspot {{
|
| 457 |
background-color: rgba(0, 150, 255, 0.8);
|
|
@@ -468,41 +479,80 @@ def create_360_viewer_html(image_paths, output_path):
|
|
| 468 |
border-radius: 3px;
|
| 469 |
padding: 5px 10px;
|
| 470 |
}}
|
| 471 |
-
#
|
| 472 |
position: absolute;
|
| 473 |
top: 10px;
|
| 474 |
left: 10px;
|
| 475 |
z-index: 1000;
|
| 476 |
background: rgba(0, 0, 0, 0.7);
|
| 477 |
color: white;
|
| 478 |
-
padding:
|
| 479 |
-
border-radius:
|
|
|
|
|
|
|
|
|
|
| 480 |
}}
|
| 481 |
-
#
|
| 482 |
-
position:
|
| 483 |
-
|
| 484 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 485 |
z-index: 1000;
|
| 486 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
color: white;
|
| 488 |
-
padding: 5px 10px;
|
| 489 |
-
border-radius: 3px;
|
| 490 |
border: none;
|
|
|
|
|
|
|
| 491 |
cursor: pointer;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 492 |
}}
|
| 493 |
</style>
|
| 494 |
</head>
|
| 495 |
<body>
|
| 496 |
-
<
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
|
|
|
|
|
|
|
|
|
| 500 |
<div id="panorama"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 501 |
|
| 502 |
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/pannellum@2.5.6/build/pannellum.js"></script>
|
| 503 |
<script>
|
| 504 |
const images = {json.dumps(image_data_list)};
|
|
|
|
| 505 |
let currentViewer = null;
|
|
|
|
| 506 |
|
| 507 |
function loadPanorama(index) {{
|
| 508 |
if (currentViewer) {{
|
|
@@ -518,6 +568,26 @@ def create_360_viewer_html(image_paths, output_path):
|
|
| 518 |
"showFullscreenCtrl": true,
|
| 519 |
"hfov": 100
|
| 520 |
}});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 521 |
}}
|
| 522 |
|
| 523 |
// Load the first image initially
|
|
@@ -525,13 +595,71 @@ def create_360_viewer_html(image_paths, output_path):
|
|
| 525 |
|
| 526 |
// Handle image selection changes
|
| 527 |
document.getElementById('image-selector').addEventListener('change', function(e) {{
|
| 528 |
-
|
|
|
|
| 529 |
}});
|
| 530 |
|
| 531 |
// Function to open the viewer in a new tab
|
| 532 |
function openInNewTab() {{
|
| 533 |
const newWindow = window.open('', '_blank');
|
| 534 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 535 |
}}
|
| 536 |
</script>
|
| 537 |
</body>
|
|
@@ -612,7 +740,6 @@ def create_fade_transition(images, fade_duration=1.0, fps=24):
|
|
| 612 |
|
| 613 |
|
| 614 |
|
| 615 |
-
# Update the process_and_display function to handle the fade animation correctly
|
| 616 |
# Update the process_and_display function to handle the fade animation correctly
|
| 617 |
def process_and_display(audio_input, generate_audio, chunk_duration):
|
| 618 |
# Validate chunk duration
|
|
@@ -636,6 +763,7 @@ def process_and_display(audio_input, generate_audio, chunk_duration):
|
|
| 636 |
group_visibility = []
|
| 637 |
all_images = [] # Collect all generated images for the fade animation
|
| 638 |
all_360_images = [] # Collect all 360 images for the viewer
|
|
|
|
| 639 |
|
| 640 |
# Process each result
|
| 641 |
for i, result in enumerate(results):
|
|
@@ -649,10 +777,11 @@ def process_and_display(audio_input, generate_audio, chunk_duration):
|
|
| 649 |
result['image_360'],
|
| 650 |
result['music']
|
| 651 |
])
|
| 652 |
-
# Collect the images
|
| 653 |
all_images.append(result['image'])
|
| 654 |
if result['image_360']:
|
| 655 |
all_360_images.append(result['image_360'])
|
|
|
|
| 656 |
else:
|
| 657 |
# If we have more results than containers, just extend with None
|
| 658 |
group_visibility.append(gr.Group(visible=False))
|
|
@@ -674,14 +803,12 @@ def process_and_display(audio_input, generate_audio, chunk_duration):
|
|
| 674 |
viewer_html_path = None
|
| 675 |
if all_360_images:
|
| 676 |
with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as tmp_file:
|
| 677 |
-
viewer_html_path = create_360_viewer_html(all_360_images, tmp_file.name)
|
| 678 |
|
| 679 |
# Hide loading indicator and show results
|
| 680 |
yield [gr.HTML("")] + group_visibility + outputs + [fade_preview, fade_animation_path, viewer_html_path, ""]
|
| 681 |
|
| 682 |
|
| 683 |
-
|
| 684 |
-
|
| 685 |
# Update the clear_all function to handle the new outputs
|
| 686 |
def clear_all():
|
| 687 |
# Create a list with None for all outputs
|
|
|
|
| 426 |
|
| 427 |
|
| 428 |
|
| 429 |
+
def create_360_viewer_html(image_paths, audio_paths, output_path):
|
| 430 |
+
"""Create an HTML file with a 360 viewer and audio player for the given images and audio."""
|
| 431 |
# Create a list of image data URIs
|
| 432 |
image_data_list = []
|
| 433 |
for img_path in image_paths:
|
|
|
|
| 435 |
img_data = base64.b64encode(f.read()).decode("utf-8")
|
| 436 |
image_data_list.append(f"data:image/jpeg;base64,{img_data}")
|
| 437 |
|
| 438 |
+
# Create a list of audio data URIs
|
| 439 |
+
audio_data_list = []
|
| 440 |
+
for audio_path in audio_paths:
|
| 441 |
+
if audio_path: # Only process if audio exists
|
| 442 |
+
with open(audio_path, "rb") as f:
|
| 443 |
+
audio_data = base64.b64encode(f.read()).decode("utf-8")
|
| 444 |
+
audio_data_list.append(f"data:audio/wav;base64,{audio_data}")
|
| 445 |
+
else:
|
| 446 |
+
audio_data_list.append(None) # Placeholder for chunks without audio
|
| 447 |
+
|
| 448 |
# Create the HTML content
|
| 449 |
html_content = f"""
|
| 450 |
<!DOCTYPE html>
|
|
|
|
| 452 |
<head>
|
| 453 |
<meta charset="UTF-8">
|
| 454 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 455 |
+
<title>360 Panorama Viewer with Audio</title>
|
| 456 |
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pannellum@2.5.6/build/pannellum.css"/>
|
| 457 |
<style>
|
| 458 |
body {{
|
| 459 |
margin: 0;
|
| 460 |
overflow: hidden;
|
| 461 |
+
font-family: Arial, sans-serif;
|
| 462 |
}}
|
| 463 |
#panorama {{
|
| 464 |
width: 100vw;
|
| 465 |
+
height: 80vh;
|
| 466 |
}}
|
| 467 |
.pnlm-hotspot.pnlm-info-hotspot {{
|
| 468 |
background-color: rgba(0, 150, 255, 0.8);
|
|
|
|
| 479 |
border-radius: 3px;
|
| 480 |
padding: 5px 10px;
|
| 481 |
}}
|
| 482 |
+
#controls {{
|
| 483 |
position: absolute;
|
| 484 |
top: 10px;
|
| 485 |
left: 10px;
|
| 486 |
z-index: 1000;
|
| 487 |
background: rgba(0, 0, 0, 0.7);
|
| 488 |
color: white;
|
| 489 |
+
padding: 10px;
|
| 490 |
+
border-radius: 5px;
|
| 491 |
+
display: flex;
|
| 492 |
+
flex-direction: column;
|
| 493 |
+
gap: 10px;
|
| 494 |
}}
|
| 495 |
+
#audio-controls {{
|
| 496 |
+
position: fixed;
|
| 497 |
+
bottom: 0;
|
| 498 |
+
left: 0;
|
| 499 |
+
width: 100%;
|
| 500 |
+
background: rgba(0, 0, 0, 0.8);
|
| 501 |
+
color: white;
|
| 502 |
+
padding: 15px;
|
| 503 |
+
display: flex;
|
| 504 |
+
flex-direction: column;
|
| 505 |
+
align-items: center;
|
| 506 |
z-index: 1000;
|
| 507 |
+
}}
|
| 508 |
+
#audio-player {{
|
| 509 |
+
width: 80%;
|
| 510 |
+
margin-bottom: 10px;
|
| 511 |
+
}}
|
| 512 |
+
#audio-info {{
|
| 513 |
+
text-align: center;
|
| 514 |
+
font-size: 14px;
|
| 515 |
+
}}
|
| 516 |
+
button {{
|
| 517 |
+
background: #3498db;
|
| 518 |
color: white;
|
|
|
|
|
|
|
| 519 |
border: none;
|
| 520 |
+
padding: 8px 15px;
|
| 521 |
+
border-radius: 3px;
|
| 522 |
cursor: pointer;
|
| 523 |
+
margin: 5px;
|
| 524 |
+
}}
|
| 525 |
+
button:hover {{
|
| 526 |
+
background: #2980b9;
|
| 527 |
+
}}
|
| 528 |
+
select {{
|
| 529 |
+
padding: 5px;
|
| 530 |
+
border-radius: 3px;
|
| 531 |
+
border: 1px solid #ccc;
|
| 532 |
}}
|
| 533 |
</style>
|
| 534 |
</head>
|
| 535 |
<body>
|
| 536 |
+
<div id="controls">
|
| 537 |
+
<select id="image-selector">
|
| 538 |
+
{"".join([f'<option value="{i}">Chunk {i+1}</option>' for i in range(len(image_data_list))])}
|
| 539 |
+
</select>
|
| 540 |
+
<button id="open-button" onclick="openInNewTab()">Open in New Tab</button>
|
| 541 |
+
</div>
|
| 542 |
+
|
| 543 |
<div id="panorama"></div>
|
| 544 |
+
|
| 545 |
+
<div id="audio-controls">
|
| 546 |
+
<audio id="audio-player" controls></audio>
|
| 547 |
+
<div id="audio-info">No audio available for this chunk</div>
|
| 548 |
+
</div>
|
| 549 |
|
| 550 |
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/pannellum@2.5.6/build/pannellum.js"></script>
|
| 551 |
<script>
|
| 552 |
const images = {json.dumps(image_data_list)};
|
| 553 |
+
const audioFiles = {json.dumps(audio_data_list)};
|
| 554 |
let currentViewer = null;
|
| 555 |
+
let currentAudioIndex = 0;
|
| 556 |
|
| 557 |
function loadPanorama(index) {{
|
| 558 |
if (currentViewer) {{
|
|
|
|
| 568 |
"showFullscreenCtrl": true,
|
| 569 |
"hfov": 100
|
| 570 |
}});
|
| 571 |
+
|
| 572 |
+
// Update audio player
|
| 573 |
+
updateAudioPlayer(index);
|
| 574 |
+
}}
|
| 575 |
+
|
| 576 |
+
function updateAudioPlayer(index) {{
|
| 577 |
+
const audioPlayer = document.getElementById('audio-player');
|
| 578 |
+
const audioInfo = document.getElementById('audio-info');
|
| 579 |
+
|
| 580 |
+
if (audioFiles[index]) {{
|
| 581 |
+
audioPlayer.src = audioFiles[index];
|
| 582 |
+
audioInfo.textContent = `Playing audio for Chunk {index + 1}`;
|
| 583 |
+
// Try to play automatically (may be blocked by browser policies)
|
| 584 |
+
audioPlayer.play().catch(e => {{
|
| 585 |
+
audioInfo.textContent = `Click play to listen to audio for Chunk {index + 1}`;
|
| 586 |
+
}});
|
| 587 |
+
}} else {{
|
| 588 |
+
audioPlayer.src = '';
|
| 589 |
+
audioInfo.textContent = 'No audio available for this chunk';
|
| 590 |
+
}}
|
| 591 |
}}
|
| 592 |
|
| 593 |
// Load the first image initially
|
|
|
|
| 595 |
|
| 596 |
// Handle image selection changes
|
| 597 |
document.getElementById('image-selector').addEventListener('change', function(e) {{
|
| 598 |
+
const selectedIndex = parseInt(e.target.value);
|
| 599 |
+
loadPanorama(selectedIndex);
|
| 600 |
}});
|
| 601 |
|
| 602 |
// Function to open the viewer in a new tab
|
| 603 |
function openInNewTab() {{
|
| 604 |
const newWindow = window.open('', '_blank');
|
| 605 |
+
const selectedIndex = document.getElementById('image-selector').value;
|
| 606 |
+
newWindow.document.write(`
|
| 607 |
+
<!DOCTYPE html>
|
| 608 |
+
<html>
|
| 609 |
+
<head>
|
| 610 |
+
<title>360 Panorama Viewer with Audio</title>
|
| 611 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pannellum@2.5.6/build/pannellum.css"/>
|
| 612 |
+
<style>
|
| 613 |
+
body {{ margin: 0; overflow: hidden; font-family: Arial, sans-serif; }}
|
| 614 |
+
#panorama {{ width: 100vw; height: 80vh; }}
|
| 615 |
+
#audio-controls {{
|
| 616 |
+
position: fixed;
|
| 617 |
+
bottom: 0;
|
| 618 |
+
left: 0;
|
| 619 |
+
width: 100%;
|
| 620 |
+
background: rgba(0, 0, 0, 0.8);
|
| 621 |
+
color: white;
|
| 622 |
+
padding: 15px;
|
| 623 |
+
display: flex;
|
| 624 |
+
flex-direction: column;
|
| 625 |
+
align-items: center;
|
| 626 |
+
z-index: 1000;
|
| 627 |
+
}}
|
| 628 |
+
#audio-player {{ width: 80%; margin-bottom: 10px; }}
|
| 629 |
+
#audio-info {{ text-align: center; font-size: 14px; }}
|
| 630 |
+
</style>
|
| 631 |
+
</head>
|
| 632 |
+
<body>
|
| 633 |
+
<div id="panorama"></div>
|
| 634 |
+
<div id="audio-controls">
|
| 635 |
+
<audio id="audio-player" controls></audio>
|
| 636 |
+
<div id="audio-info">Audio for Chunk {parseInt(selectedIndex) + 1}</div>
|
| 637 |
+
</div>
|
| 638 |
+
<script src="https://cdn.jsdelivr.net/npm/pannellum@2.5.6/build/pannellum.js"><\/script>
|
| 639 |
+
<script>
|
| 640 |
+
pannellum.viewer('panorama', {{
|
| 641 |
+
"type": "equirectangular",
|
| 642 |
+
"panorama": "{images[selectedIndex]}",
|
| 643 |
+
"autoLoad": true,
|
| 644 |
+
"autoRotate": -2,
|
| 645 |
+
"showZoomCtrl": true,
|
| 646 |
+
"showFullscreenCtrl": true,
|
| 647 |
+
"hfov": 100
|
| 648 |
+
}});
|
| 649 |
+
|
| 650 |
+
const audioPlayer = document.getElementById('audio-player');
|
| 651 |
+
if ("{audioFiles[selectedIndex]}") {{
|
| 652 |
+
audioPlayer.src = "{audioFiles[selectedIndex]}";
|
| 653 |
+
audioPlayer.play().catch(e => {{
|
| 654 |
+
document.getElementById('audio-info').textContent = 'Click play to listen to audio';
|
| 655 |
+
}});
|
| 656 |
+
}} else {{
|
| 657 |
+
document.getElementById('audio-info').textContent = 'No audio available for this chunk';
|
| 658 |
+
}}
|
| 659 |
+
<\/script>
|
| 660 |
+
</body>
|
| 661 |
+
</html>
|
| 662 |
+
`);
|
| 663 |
}}
|
| 664 |
</script>
|
| 665 |
</body>
|
|
|
|
| 740 |
|
| 741 |
|
| 742 |
|
|
|
|
| 743 |
# Update the process_and_display function to handle the fade animation correctly
|
| 744 |
def process_and_display(audio_input, generate_audio, chunk_duration):
|
| 745 |
# Validate chunk duration
|
|
|
|
| 763 |
group_visibility = []
|
| 764 |
all_images = [] # Collect all generated images for the fade animation
|
| 765 |
all_360_images = [] # Collect all 360 images for the viewer
|
| 766 |
+
all_music_paths = [] # Collect all music paths for the viewer
|
| 767 |
|
| 768 |
# Process each result
|
| 769 |
for i, result in enumerate(results):
|
|
|
|
| 777 |
result['image_360'],
|
| 778 |
result['music']
|
| 779 |
])
|
| 780 |
+
# Collect the images and music
|
| 781 |
all_images.append(result['image'])
|
| 782 |
if result['image_360']:
|
| 783 |
all_360_images.append(result['image_360'])
|
| 784 |
+
all_music_paths.append(result['music']) # Can be None if no music generated
|
| 785 |
else:
|
| 786 |
# If we have more results than containers, just extend with None
|
| 787 |
group_visibility.append(gr.Group(visible=False))
|
|
|
|
| 803 |
viewer_html_path = None
|
| 804 |
if all_360_images:
|
| 805 |
with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as tmp_file:
|
| 806 |
+
viewer_html_path = create_360_viewer_html(all_360_images, all_music_paths, tmp_file.name)
|
| 807 |
|
| 808 |
# Hide loading indicator and show results
|
| 809 |
yield [gr.HTML("")] + group_visibility + outputs + [fade_preview, fade_animation_path, viewer_html_path, ""]
|
| 810 |
|
| 811 |
|
|
|
|
|
|
|
| 812 |
# Update the clear_all function to handle the new outputs
|
| 813 |
def clear_all():
|
| 814 |
# Create a list with None for all outputs
|