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)