import streamlit as st import os import glob import time import base64 from streamlit.components.v1 import html from streamlit_autorefresh import st_autorefresh # 페이지 설정 st.set_page_config( page_title="디스플레이 컨텐츠 관리", page_icon="🖼️", layout="wide", initial_sidebar_state="collapsed" ) # 전역 CSS 설정 st.markdown(""" """, unsafe_allow_html=True) # 전체 화면 JS 함수 def inject_fullscreen_js(): js_code = """ """ html(js_code, height=0, width=0) # 배경 이미지 표시 방식 전환을 위한 JavaScript 함수 def inject_background_toggle_js(): js_code = """ """ html(js_code, height=0, width=0) # URL 파라미터 처리 try: query_params = st.query_params # 전체화면 모드 확인 if "fullscreen" in query_params and query_params["fullscreen"][0].lower() == "true": st.session_state.preview_mode = True st.session_state.activate_fullscreen = True # 이미지 전환 모드 확인 if "change" in query_params: st.session_state.fixed_image_enabled = False # 단일 이미지 모드 확인 elif "1image" in query_params: # change와 1image 둘 다 있으면 change가 우선하도록 elif 사용 st.session_state.fixed_image_enabled = True # 특정 이미지 인덱스가 지정되었는지 확인 if "image" in query_params: try: img_index = int(query_params["image"]) # contents 폴더 내의 모든 이미지 파일 미리 로드 if not os.path.exists('contents'): os.makedirs('contents') image_files = glob.glob('contents/*.jpg') + glob.glob('contents/*.jpeg') + glob.glob('contents/*.png') # 인덱스가 유효한지 확인 if 0 <= img_index < len(image_files): st.session_state.fixed_image_path = image_files[img_index] elif image_files: # 유효하지 않지만 이미지가 있는 경우 첫 번째 이미지 사용 st.session_state.fixed_image_path = image_files[0] except ValueError: # 숫자로 변환할 수 없는 경우 pass except Exception as e: st.error(f"URL 파라미터 처리 중 오류 발생: {e}") # 초기 설정 및 상태 관리 (기존 코드 수정) if 'contents' not in st.session_state: # contents 폴더 내의 모든 이미지 파일 로드 if not os.path.exists('contents'): os.makedirs('contents') image_files = glob.glob('contents/*.jpg') + glob.glob('contents/*.jpeg') + glob.glob('contents/*.png') st.session_state.contents = image_files if 'current_image_index' not in st.session_state: st.session_state.current_image_index = 0 if 'animation_type' not in st.session_state: st.session_state.animation_type = 'animate-fade' if 'activate_fullscreen' not in st.session_state: st.session_state.activate_fullscreen = False if 'fit_mode' not in st.session_state: st.session_state.fit_mode = 'cover' # 기본값은 화면에 꽉 차게 if 'app_loaded' not in st.session_state: st.session_state.app_loaded = False # 기존 코드를 조건부로 수정 if 'fixed_image_enabled' not in st.session_state: st.session_state.fixed_image_enabled = False if 'fixed_image_path' not in st.session_state and st.session_state.contents: st.session_state.fixed_image_path = st.session_state.contents[0] if st.session_state.contents else None # 슬라이드쇼 자동 시작 로직 if not st.session_state.app_loaded and not st.session_state.get('preview_mode', False): if st.session_state.contents and not st.session_state.fixed_image_enabled: # 이미지가 있고 고정 모드가 아닌 경우에만 자동 시작 st.session_state.preview_mode = True st.session_state.preview_start_time = time.time() st.session_state.activate_fullscreen = True st.session_state.app_loaded = True # 앱이 로드되었음을 표시 # 사이드바 - 이미지 관리 with st.sidebar: st.title("📸 디스플레이 설정") # 이미지 업로드 st.subheader("이미지 추가") uploaded_files = st.file_uploader("이미지 파일을 업로드하세요", type=["jpg", "jpeg", "png"], accept_multiple_files=True) if uploaded_files: for uploaded_file in uploaded_files: # contents 디렉토리가 없으면 생성 if not os.path.exists('contents'): os.makedirs('contents') # 저장 경로 생성 file_path = os.path.join('contents', uploaded_file.name) # 이미지 저장 with open(file_path, "wb") as f: f.write(uploaded_file.getvalue()) # 저장된 이미지를 목록에 추가 (중복 방지) if file_path not in st.session_state.contents: st.session_state.contents.append(file_path) st.success(f"{len(uploaded_files)}개 이미지가 추가되었습니다.") # 이미지 목록 및 관리 st.subheader("이미지 순서 조정") if not st.session_state.contents: st.info("등록된 이미지가 없습니다. 이미지를 추가해주세요.") else: # 이미지 목록 표시 및 순서 조정 for i, img_path in enumerate(st.session_state.contents): col1, col2 = st.columns([3, 1]) with col1: st.text(os.path.basename(img_path)) st.image(img_path, width=150) with col2: # 위로 이동 버튼 if i > 0 and st.button("👆", key=f"up_{i}"): st.session_state.contents[i], st.session_state.contents[i-1] = st.session_state.contents[i-1], st.session_state.contents[i] st.rerun() # 아래로 이동 버튼 if i < len(st.session_state.contents) - 1 and st.button("👇", key=f"down_{i}"): st.session_state.contents[i], st.session_state.contents[i+1] = st.session_state.contents[i+1], st.session_state.contents[i] st.rerun() # 삭제 버튼 if st.button("🗑️", key=f"delete_{i}"): del st.session_state.contents[i] st.rerun() st.markdown("
", unsafe_allow_html=True) # 이미지 고정 설정 st.subheader("이미지 고정 설정") fixed_image_enabled = st.checkbox( "한 이미지만 고정하여 표시", value=st.session_state.fixed_image_enabled, help="체크하면 선택한 이미지만 계속 표시됩니다. 슬라이드쇼가 중지됩니다." ) # 체크박스 상태가 변경되었을 때 세션 상태 업데이트 if fixed_image_enabled != st.session_state.fixed_image_enabled: st.session_state.fixed_image_enabled = fixed_image_enabled # 고정 모드가 비활성화되면 슬라이드쇼 재개 if not fixed_image_enabled and st.session_state.get('preview_mode', False): st.session_state.preview_start_time = time.time() # 이미지 고정 모드가 활성화된 경우에만 이미지 선택 드롭다운 표시 if st.session_state.fixed_image_enabled: # 이미지 파일 경로에서 파일명만 추출하여 표시 옵션 생성 image_options = {img_path: os.path.basename(img_path) for img_path in st.session_state.contents} # 선택된 이미지가 없거나 목록에 없는 경우 첫 번째 이미지 선택 default_index = 0 if st.session_state.fixed_image_path in image_options: default_index = list(image_options.keys()).index(st.session_state.fixed_image_path) # 이미지 선택 드롭다운 if st.session_state.contents: # 이미지가 있는 경우에만 드롭다운 표시 selected_image = st.selectbox( "고정할 이미지 선택", options=list(image_options.keys()), format_func=lambda x: image_options[x], index=default_index ) # 선택된 이미지 저장 st.session_state.fixed_image_path = selected_image # 선택된 이미지 미리보기 표시 st.image(selected_image, caption="선택된 이미지", width=200) else: st.warning("표시할 이미지가 없습니다. 이미지를 추가해주세요.") # 애니메이션 효과 선택 (계속) animation_options = { 'animate-fade': '페이드 인/아웃', 'animate-zoom': '줌 인/아웃', 'animate-slide': '슬라이드', 'animate-rotate': '회전' } selected_animation = st.radio( "전환 효과 선택", options=list(animation_options.keys()), format_func=lambda x: animation_options[x], index=list(animation_options.keys()).index(st.session_state.animation_type) if st.session_state.animation_type in animation_options else 0 ) if selected_animation != st.session_state.animation_type: st.session_state.animation_type = selected_animation # 재생 설정 st.subheader("재생 설정") # 이미지 표시 모드 선택 fit_options = { 'cover': '화면에 꽉 차게', 'contain': '원본 비율 유지' } selected_fit = st.radio( "이미지 표시 방식", options=list(fit_options.keys()), format_func=lambda x: fit_options[x], index=list(fit_options.keys()).index(st.session_state.fit_mode) if st.session_state.fit_mode in fit_options else 0 ) if selected_fit != st.session_state.fit_mode: st.session_state.fit_mode = selected_fit # 슬라이드쇼 간격 설정 preview_interval = st.slider("이미지 전환 간격 (초)", min_value=1, max_value=60, value=10) # 슬라이드쇼 시작/종료 버튼 col1, col2 = st.columns(2) with col1: if st.button("🖼️ 미리보기 시작", use_container_width=True): st.session_state.preview_mode = True st.session_state.preview_start_time = time.time() st.session_state.activate_fullscreen = False st.rerun() with col2: if st.button("🔍 전체화면 보기", use_container_width=True): st.session_state.preview_mode = True st.session_state.preview_start_time = time.time() st.session_state.activate_fullscreen = True st.rerun() # 미리보기 모드인 경우 종료 버튼 표시 if st.session_state.get('preview_mode', False): if st.button("⏹️ 미리보기 종료", use_container_width=True): st.session_state.preview_mode = False st.session_state.activate_fullscreen = False st.rerun() # 사이드바 하단에 URL 정보 추가 st.sidebar.markdown("---") st.sidebar.subheader("접속 URL") base_url = "https://jamespark3-display.hf.space/" # 이미지 전환 모드 URL transition_url = f"{base_url}?fullscreen=true&change" st.sidebar.text_area("이미지 전환 모드", transition_url, help="이 URL로 접속하면 자동으로 이미지 전환 모드로 시작됩니다.") # 단일 이미지 모드 URL if st.session_state.fixed_image_enabled and st.session_state.fixed_image_path and st.session_state.contents: current_img_index = st.session_state.contents.index(st.session_state.fixed_image_path) if st.session_state.fixed_image_path in st.session_state.contents else 0 single_url = f"{base_url}?fullscreen=true&1image&image={current_img_index}" st.sidebar.text_area("현재 선택된 이미지 모드", single_url, help="이 URL로 접속하면 선택된 이미지만 표시됩니다.") else: single_url = f"{base_url}?fullscreen=true&1image" st.sidebar.text_area("단일 이미지 모드", single_url, help="이 URL로 접속하면 첫 번째 이미지가 고정되어 표시됩니다.") # 메인 컨텐츠 영역 if st.session_state.get('preview_mode', False): # 허깅페이스 UI 요소 숨기기 위한 CSS 추가 st.markdown(""" """, unsafe_allow_html=True) # 자동 새로고침을 위한 카운터 count = st_autorefresh(interval=preview_interval * 1000, key="refreshInterval") # 배경 이미지 전환 JS 삽입 (먼저 실행되어야 함) inject_background_toggle_js() # 전체화면 활성화 필요시 실행 if st.session_state.activate_fullscreen: inject_fullscreen_js() # 현재 인덱스 업데이트 if count > 0 and st.session_state.contents: # 이미지 고정 모드가 활성화되지 않은 경우에만 이미지 변경 if not st.session_state.fixed_image_enabled: st.session_state.current_image_index = (st.session_state.current_image_index + 1) % len(st.session_state.contents) # 화면에 배경 이미지로 표시 if st.session_state.contents: # 이미지 고정 모드가 활성화된 경우 고정 이미지 사용, 아니면 현재 인덱스 이미지 사용 if st.session_state.fixed_image_enabled and st.session_state.fixed_image_path: full_img_path = st.session_state.fixed_image_path else: full_img_path = st.session_state.contents[st.session_state.current_image_index] # 웹에서 접근 가능한 이미지 URL로 변환 (Base64 인코딩 사용) with open(full_img_path, "rb") as img_file: img_data = base64.b64encode(img_file.read()).decode() # 이미지 확장자 가져오기 (기본값은 jpeg) file_ext = os.path.splitext(full_img_path)[1][1:] or "jpeg" # 배경 이미지 설정과 컨트롤 버튼이 포함된 HTML 생성 background_html = f"""
""" # HTML 직접 삽입 st.markdown(background_html, unsafe_allow_html=True) # Streamlit 요소 숨기기 st.markdown(""" """, unsafe_allow_html=True) else: # 일반 모드 - 앱 소개 및 사용법 st.title("🖼️ 디지털 디스플레이 컨텐츠 관리") if not st.session_state.contents: st.warning("⚠️ 등록된 이미지가 없습니다. 사이드바에서 이미지를 추가해주세요.") else: st.success(f"✅ {len(st.session_state.contents)}개의 이미지가 등록되어 있습니다.") st.markdown(""" ### 사용 방법 1. 왼쪽 사이드바에서 이미지를 추가하세요. 2. 이미지 순서를 조정하고 전환 효과를 선택하세요. 3. 재생 설정에서 이미지 표시 방식과 전환 간격을 설정하세요. 4. 미리보기 시작 또는 전체화면 보기 버튼을 클릭하여 슬라이드쇼를 시작하세요. ### 기능 소개 - **이미지 순서 조정**: 👆👇 버튼으로 이미지 순서를 변경할 수 있습니다. - **이미지 고정**: 특정 이미지 하나만 계속 표시하도록 설정할 수 있습니다. - **전환 효과**: 페이드, 줌, 슬라이드, 회전 효과를 선택할 수 있습니다. - **표시 방식**: 화면에 꽉 차게 또는 원본 비율을 유지하며 표시할 수 있습니다. - **자동 전환**: 설정한 간격으로 이미지가 자동 전환됩니다. ### 이미지 미리보기 """) # 이미지 그리드로 표시 if st.session_state.contents: cols = st.columns(4) for i, img_path in enumerate(st.session_state.contents): with cols[i % 4]: # use_column_width=True를 use_container_width=True로 변경 st.image(img_path, caption=os.path.basename(img_path), use_container_width=True)