haepada commited on
Commit
541e490
·
verified ·
1 Parent(s): 41b2fe4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +212 -82
app.py CHANGED
@@ -577,7 +577,7 @@ def safe_state_update(state, updates):
577
  def create_interface():
578
  import base64
579
 
580
- # initial_state 정의 추가
581
  initial_state = {
582
  "user_name": "",
583
  "baseline_features": None,
@@ -585,63 +585,106 @@ def create_interface():
585
  "wish": None,
586
  "final_prompt": "",
587
  "image_path": None,
588
- "current_tab": 0
 
589
  }
590
 
591
- def get_image_base64(image_path):
592
- with open(image_path, "rb") as img_file:
593
- return base64.b64encode(img_file.read()).decode()
594
-
595
- # 모바일/데스크톱 로고 미리 인코딩
596
- try:
597
- mobile_logo = get_image_base64("static/DIGITAL_GUTPAN_LOGO_m.png")
598
- desktop_logo = get_image_base64("static/DIGITAL_GUTPAN_LOGO_w.png")
599
- except Exception as e:
600
- print(f"로고 로딩 에러: {e}")
601
- mobile_logo = ""
602
- desktop_logo = "" # Ensure an empty string is assigned to desktop_logo in case of error
603
-
 
604
 
605
- css = """
606
- /* 모바일 뷰 */
607
- @media (max-width: 600px) {
608
- .logo-container {
609
- padding: 20px 10px !important;
610
- width: 100% !important;
611
- }
612
- .desktop-logo { display: none !important; }
613
- .mobile-logo {
614
- width: 100% !important;
615
- height: auto !important;
616
- max-width: 300px !important;
617
- margin: 0 auto !important;
618
- display: block !important;
619
  }
620
  }
621
 
622
- /* 데스크톱 */
623
- @media (min-width: 601px) {
624
- .logo-container {
625
- padding: 20px 0;
626
- width: 100%;
627
- max-width: 800px;
628
- margin: 0 auto;
629
- }
630
- .mobile-logo { display: none !important; }
631
- .desktop-logo {
632
- width: 100%;
633
- height: auto;
634
- max-width: 800px;
635
- margin: 0 auto;
636
- display: block;
637
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
638
  }
639
 
640
- /* 공통 스타일 */
641
- .logo-container {
642
- background: transparent !important;
643
- text-align: center;
644
- margin-bottom: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
645
  }
646
  """
647
 
@@ -712,35 +755,33 @@ def create_interface():
712
  💫 이 앱은 온천천의 사운드스케이프를 녹음하여 제작되었으며,
713
  온천천 온천장역에서 장전역까지 걸으며 더 깊은 체험이 가능합니다.
714
  """)
715
- play_music_btn = gr.Button("온천천의 소리 듣기", variant="secondary")
716
- with gr.Row():
717
- audio = gr.Audio(
718
- value="assets/main_music.mp3",
719
- type="filepath",
720
- label="온천천의 소리",
 
 
 
 
 
 
 
 
 
721
  interactive=False,
722
- show_download_button=True,
723
- visible=True
724
  )
725
- with gr.Column():
726
- reflection_input = gr.Textbox(
727
- label="지금 이 순간의 감상을 자유롭게 적어보세요",
728
- lines=3,
729
- max_lines=5
730
- )
731
- save_btn = gr.Button("감상 저장하기", variant="secondary")
732
- reflections_display = gr.Dataframe(
733
- headers=["시간", "감상", "감정 분석"],
734
- label="기록된 감상들",
735
- value=[], # 초기값은 빈 리스트
736
- interactive=False,
737
- wrap=True,
738
- row_count=(5, "dynamic") # 동적으로 행 수 조정
739
- )
740
 
741
  # 기원 탭
742
  with gr.TabItem("기원") as tab_wish:
743
  gr.Markdown("## 기원 - 소원을 전해보세요")
 
 
 
 
744
  with gr.Row():
745
  with gr.Column():
746
  voice_input = gr.Audio(
@@ -944,7 +985,29 @@ def create_interface():
944
  except Exception as e:
945
  print(f"Error saving wish: {e}")
946
  return "오류가 발생했습니다.", []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
947
 
 
948
  # 이벤트 연결
949
  name_submit_btn.click(
950
  fn=handle_name_submit,
@@ -985,9 +1048,9 @@ def create_interface():
985
  )
986
 
987
  analyze_btn.click(
988
- fn=analyze_voice,
989
  inputs=[voice_input, state],
990
- outputs=[state, transcribed_text, voice_emotion, text_emotion, final_prompt]
991
  )
992
 
993
  generate_btn.click(
@@ -1004,16 +1067,83 @@ def create_interface():
1004
  return app
1005
 
1006
  if __name__ == "__main__":
1007
- # 필요한 디렉토리 생성
1008
- for directory in ['static/icons', 'assets', 'templates', 'data', 'generated_images']:
1009
- os.makedirs(directory, exist_ok=True)
1010
-
1011
- # PWA 파일 생���
1012
- create_pwa_files()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1013
 
1014
- # Gradio 인터페이스 생성 및 실행
1015
  demo = create_interface()
1016
- demo.launch(
1017
  server_name="0.0.0.0",
1018
  server_port=7860,
1019
  share=True,
 
577
  def create_interface():
578
  import base64
579
 
580
+ # initial_state 정의
581
  initial_state = {
582
  "user_name": "",
583
  "baseline_features": None,
 
585
  "wish": None,
586
  "final_prompt": "",
587
  "image_path": None,
588
+ "current_tab": 0,
589
+ "audio_playing": False # 오디오 상태 추가
590
  }
591
 
592
+ # HTML5 Audio Player 템플릿
593
+ AUDIO_PLAYER_HTML = """
594
+ <div class="audio-player-container">
595
+ <audio id="mainAudio" preload="auto" style="width: 100%;">
596
+ <source src="/assets/main_music.mp3" type="audio/mp3">
597
+ Your browser does not support the audio element.
598
+ </audio>
599
+ <button id="playButton" onclick="togglePlay()" class="custom-audio-button">
600
+ 재생/일시정지
601
+ </button>
602
+ </div>
603
+ <script>
604
+ let audioElement = document.getElementById('mainAudio');
605
+ let isPlaying = false;
606
 
607
+ function togglePlay() {
608
+ if (!isPlaying) {
609
+ audioElement.play()
610
+ .then(() => {
611
+ isPlaying = true;
612
+ })
613
+ .catch(error => {
614
+ console.error("Audio playback failed:", error);
615
+ alert("음악 재생에 실패했습니다. 다시 시도해주세요.");
616
+ });
617
+ } else {
618
+ audioElement.pause();
619
+ isPlaying = false;
 
620
  }
621
  }
622
 
623
+ // 페이지 벗어날 때 오디오 정지
624
+ window.addEventListener('beforeunload', function() {
625
+ if (audioElement) {
626
+ audioElement.pause();
627
+ audioElement.currentTime = 0;
 
 
 
 
 
 
 
 
 
 
628
  }
629
+ });
630
+
631
+ // 오디오 로딩 에러 처리
632
+ audioElement.addEventListener('error', function(e) {
633
+ console.error("Audio error:", e);
634
+ alert("음악 파일을 불러오는데 실패했습니다. 페이지를 새로고침해주세요.");
635
+ });
636
+ </script>
637
+ <style>
638
+ .custom-audio-button {
639
+ width: 100%;
640
+ padding: 10px;
641
+ margin: 10px 0;
642
+ background-color: #4a90e2;
643
+ color: white;
644
+ border: none;
645
+ border-radius: 5px;
646
+ cursor: pointer;
647
+ font-size: 16px;
648
+ }
649
+ .custom-audio-button:active {
650
+ background-color: #357abd;
651
+ }
652
+ .audio-player-container {
653
+ margin: 20px 0;
654
+ width: 100%;
655
+ }
656
+ </style>
657
+ """
658
+
659
+ css = """
660
+ /* 기존 CSS 유지 */
661
+ .gradio-container {
662
+ max-width: 100% !important;
663
+ padding: 0 !important;
664
+ }
665
+
666
+ .audio-container {
667
+ margin: 20px 0;
668
+ width: 100%;
669
  }
670
 
671
+ /* 모바일 최적화 */
672
+ @media (max-width: 600px) {
673
+ .gradio-button {
674
+ min-height: 44px !important; /* iOS 터치 타겟 사이즈 */
675
+ margin: 10px 0 !important;
676
+ }
677
+
678
+ .container {
679
+ padding: 10px !important;
680
+ }
681
+
682
+ /* 입력 필드 최적화 */
683
+ input[type="text"],
684
+ textarea {
685
+ font-size: 16px !important; /* iOS auto-zoom 방지 */
686
+ padding: 12px !important;
687
+ }
688
  }
689
  """
690
 
 
755
  💫 이 앱은 온천천의 사운드스케이프를 녹음하여 제작되었으며,
756
  온천천 온천장역에서 장전역까지 걸으며 더 깊은 체험이 가능합니다.
757
  """)
758
+
759
+ # 커스텀 오디오 플레이어로 교체
760
+ gr.HTML(AUDIO_PLAYER_HTML)
761
+
762
+ with gr.Column():
763
+ reflection_input = gr.Textbox(
764
+ label="지금 이 순간의 감상을 자유롭게 적어보세요",
765
+ lines=3,
766
+ max_lines=5
767
+ )
768
+ save_btn = gr.Button("감상 저장하기", variant="secondary")
769
+ reflections_display = gr.Dataframe(
770
+ headers=["시간", "감상", "감정 분석"],
771
+ label="기록된 감상들",
772
+ value=[],
773
  interactive=False,
774
+ wrap=True,
775
+ row_count=(5, "dynamic")
776
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
777
 
778
  # 기원 탭
779
  with gr.TabItem("기원") as tab_wish:
780
  gr.Markdown("## 기원 - 소원을 전해보세요")
781
+
782
+ # 상태 표시 추가
783
+ processing_status = gr.Markdown("", visible=False)
784
+
785
  with gr.Row():
786
  with gr.Column():
787
  voice_input = gr.Audio(
 
985
  except Exception as e:
986
  print(f"Error saving wish: {e}")
987
  return "오류가 발생했습니다.", []
988
+
989
+ def safe_analyze_voice(audio_data, state):
990
+ """음성 분석 함수에 안전장치 추가"""
991
+ if audio_data is None:
992
+ return state, "음성을 먼저 녹음해주세요.", "", "", "", gr.update(visible=False)
993
+
994
+ try:
995
+ processing_status.update(value="분석 중입니다...", visible=True)
996
+ result = analyze_voice(audio_data, state)
997
+ processing_status.update(value="", visible=False)
998
+ return (*result, gr.update(visible=False))
999
+ except Exception as e:
1000
+ print(f"Voice analysis error: {str(e)}")
1001
+ return (
1002
+ state,
1003
+ "음성 분석 중 오류가 발생했습니다. 다시 시도해주세요.",
1004
+ "",
1005
+ "",
1006
+ "",
1007
+ gr.update(visible=False)
1008
+ )
1009
 
1010
+
1011
  # 이벤트 연결
1012
  name_submit_btn.click(
1013
  fn=handle_name_submit,
 
1048
  )
1049
 
1050
  analyze_btn.click(
1051
+ fn=safe_analyze_voice,
1052
  inputs=[voice_input, state],
1053
+ outputs=[state, transcribed_text, voice_emotion, text_emotion, final_prompt, processing_status]
1054
  )
1055
 
1056
  generate_btn.click(
 
1067
  return app
1068
 
1069
  if __name__ == "__main__":
1070
+ # 서비스 워커 캐시 설정 강화
1071
+ sw_content = """
1072
+ const CACHE_NAME = 'digital-gutpan-v2';
1073
+ const urlsToCache = [
1074
+ '/',
1075
+ '/assets/main_music.mp3',
1076
+ '/static/icons/icon-72x72.png',
1077
+ '/static/icons/icon-96x96.png',
1078
+ '/static/icons/icon-128x128.png',
1079
+ '/static/icons/icon-144x144.png',
1080
+ '/static/icons/icon-152x152.png',
1081
+ '/static/icons/icon-192x192.png',
1082
+ '/static/icons/icon-384x384.png',
1083
+ '/static/icons/icon-512x512.png'
1084
+ ];
1085
+
1086
+ self.addEventListener('install', event => {
1087
+ event.waitUntil(
1088
+ caches.open(CACHE_NAME)
1089
+ .then(cache => {
1090
+ console.log('Opened cache');
1091
+ return cache.addAll(urlsToCache);
1092
+ })
1093
+ .then(() => self.skipWaiting())
1094
+ );
1095
+ });
1096
+
1097
+ self.addEventListener('activate', event => {
1098
+ event.waitUntil(
1099
+ caches.keys().then(cacheNames => {
1100
+ return Promise.all(
1101
+ cacheNames.map(cacheName => {
1102
+ if (cacheName !== CACHE_NAME) {
1103
+ return caches.delete(cacheName);
1104
+ }
1105
+ })
1106
+ );
1107
+ }).then(() => self.clients.claim())
1108
+ );
1109
+ });
1110
+
1111
+ self.addEventListener('fetch', event => {
1112
+ event.respondWith(
1113
+ caches.match(event.request)
1114
+ .then(response => {
1115
+ if (response) {
1116
+ return response;
1117
+ }
1118
+
1119
+ return fetch(event.request).then(
1120
+ response => {
1121
+ if(!response || response.status !== 200 || response.type !== 'basic') {
1122
+ return response;
1123
+ }
1124
+
1125
+ const responseToCache = response.clone();
1126
+
1127
+ caches.open(CACHE_NAME)
1128
+ .then(cache => {
1129
+ cache.put(event.request, responseToCache);
1130
+ });
1131
+
1132
+ return response;
1133
+ }
1134
+ );
1135
+ })
1136
+ );
1137
+ });
1138
+ """
1139
+
1140
+ # 서비스 워커 파일 생성
1141
+ with open('static/service-worker.js', 'w') as f:
1142
+ f.write(sw_content)
1143
 
1144
+ # Gradio 실행
1145
  demo = create_interface()
1146
+ demo.queue(concurrency_count=4).launch(
1147
  server_name="0.0.0.0",
1148
  server_port=7860,
1149
  share=True,