aianyu commited on
Commit
fa24c89
·
verified ·
1 Parent(s): 75fe6bc

Upload 7 files

Browse files
Files changed (7) hide show
  1. 2020.icc +0 -0
  2. 6172.icc +0 -0
  3. Dockerfile +27 -0
  4. README.md +38 -5
  5. app.py +206 -0
  6. qingwa.icc +0 -0
  7. requirements.txt +2 -0
2020.icc ADDED
Binary file (9.18 kB). View file
 
6172.icc ADDED
Binary file (30.8 kB). View file
 
Dockerfile ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用官方的 Python 基础镜像
2
+ FROM python:3.9-slim
3
+
4
+ # 设置工作目录
5
+ WORKDIR /app
6
+
7
+ # 将依赖文件复制到工作目录
8
+ COPY requirements.txt ./
9
+
10
+ # 安装依赖
11
+ # --no-cache-dir 减少镜像大小
12
+ # --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --trusted-host pypi.org 是为了解决某些网络环境下可能的SSL问题
13
+ RUN pip install --no-cache-dir --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --trusted-host pypi.org -r requirements.txt
14
+
15
+ # 将当前目录下的所有文件(app.py, .icc 文件等)复制到工作目录
16
+ COPY . .
17
+
18
+ # 暴露 Streamlit 默认端口
19
+ EXPOSE 8501
20
+
21
+ # 设置健康检查 (Hugging Face Spaces 会用到)
22
+ HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
23
+
24
+ # 运行 Streamlit 应用
25
+ # 使用 exec form of CMD, and ensure server binds to 0.0.0.0
26
+ ENTRYPOINT ["streamlit", "run"]
27
+ CMD ["app.py", "--server.port=8501", "--server.address=0.0.0.0"]
README.md CHANGED
@@ -1,10 +1,43 @@
1
  ---
2
- title: Hdr
3
- emoji: 📊
4
- colorFrom: green
5
- colorTo: indigo
6
  sdk: docker
 
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: 发光青蛙表情包 HDR 制作
3
+ emoji: 🐸️
4
+ colorFrom: indigo
5
+ colorTo: blue
6
  sdk: docker
7
+ app_port: 8501
8
  pinned: false
9
+ # Python Version (for information, Dockerfile controls the actual version)
10
+ python_version: 3.9
11
  ---
12
 
13
+ # 🖼️ ICC Profile Embedder
14
+
15
+ 这是一个 Streamlit Web 应用,允许用户上传一个或多个图片文件,并嵌入选定的 ICC 颜色配置文件。
16
+
17
+ ## 功能
18
+
19
+ * **选择 ICC Profile**: 用户可以从以下预设的 ICC 配置文件中选择一个:
20
+ * 原版青蛙 - 默认
21
+ * 超亮版本
22
+ * 多兼容版本
23
+ * **图片上传**: 支持上传 JPG, PNG, TIFF 格式的图片。
24
+ * **ICC 嵌入**: 将选定的 ICC Profile 嵌入到每一张上传的图片中。
25
+ * **结果展示**: 处理后的图片会在各自的标签页 (Tab) 中展示。
26
+ * **单张下载**: 每个处理后的图片都有独立的下载按钮。
27
+ * **批量下载**: 如果上传并处理了多张图片,提供一个 ZIP 压缩包下载所有处理后图片的功能。
28
+
29
+ ## 如何使用
30
+
31
+ 1. 在页面顶部选择一个你想要嵌入的 ICC 配置文件。
32
+ 2. 点击 "Browse files" 或拖拽图片到上传区域以上传一个或多个图片。
33
+ 3. 应用会自动处理图片,并在下方的标签页中显示结果。
34
+ 4. 你可以点击每个图片下方的下载按钮下载单张图片,或者点击页面底部的 "下载全部 ZIP" 按钮下载所有处理后的图片。
35
+
36
+ ## 技术栈
37
+
38
+ * Streamlit
39
+ * Pillow (PIL Fork)
40
+ * Docker
41
+
42
+ ---
43
+ 由 [aianyu] 创建 (可选)
app.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from PIL import Image, UnidentifiedImageError
3
+ import io
4
+ import os
5
+ import zipfile
6
+
7
+ # --- 配置 ---
8
+ ICC_PROFILE_OPTIONS = {
9
+ "原版青蛙": "qingwa.icc",
10
+ "超亮版本": "6172.icc",
11
+ "多兼容版本": "2020.icc",
12
+ }
13
+ DEFAULT_ICC_KEY = "原版青蛙"
14
+ SUPPORTED_FORMATS = ["jpg", "jpeg", "png", "tif", "tiff"]
15
+
16
+
17
+ # --- 辅助函数 ---
18
+
19
+ def get_icc_profile_bytes(selected_icc_filename):
20
+ script_dir = os.path.dirname(__file__)
21
+ icc_profile_path = os.path.join(script_dir, selected_icc_filename)
22
+ if not os.path.exists(icc_profile_path):
23
+ st.error(f"错误: ICC 配置文件 '{selected_icc_filename}' 未在脚本目录 '{script_dir}' 中找到。")
24
+ return None
25
+ try:
26
+ with open(icc_profile_path, "rb") as f:
27
+ return f.read()
28
+ except Exception as e:
29
+ st.error(f"读取 ICC 文件 '{selected_icc_filename}' 时出错: {e}")
30
+ return None
31
+
32
+
33
+ def embed_icc_profile(image_bytes, icc_profile_bytes, original_filename):
34
+ if icc_profile_bytes is None:
35
+ st.error(f"由于无法加载ICC配置文件,未能处理图片 '{original_filename}'。")
36
+ return None, None
37
+ try:
38
+ img = Image.open(io.BytesIO(image_bytes))
39
+ img_format = img.format if img.format else "PNG"
40
+
41
+ original_mode = img.mode
42
+ exif_data = img.info.get("exif")
43
+
44
+ if img.mode == 'P':
45
+ has_transparency = "transparency" in img.info
46
+ img = img.convert('RGBA' if has_transparency else 'RGB')
47
+
48
+ output_image_bytes = io.BytesIO()
49
+ params = {"icc_profile": icc_profile_bytes}
50
+ if exif_data:
51
+ params["exif"] = exif_data
52
+
53
+ if img_format.upper() == "JPEG":
54
+ params["quality"] = 95
55
+ params["optimize"] = True
56
+
57
+ try:
58
+ img.save(output_image_bytes, format=img_format, **params)
59
+ except Exception as e_save:
60
+ st.warning(
61
+ f"无法以格式 '{img_format}' (模式: {original_mode}) 保存 '{original_filename}' (原因: {e_save})。尝试转换为兼容模式并以PNG格式保存...")
62
+
63
+ img_reloaded = Image.open(io.BytesIO(image_bytes))
64
+ target_mode = 'RGB'
65
+ if img_reloaded.mode in ['LA', 'RGBA', 'PA'] or \
66
+ (img_reloaded.mode == 'P' and "transparency" in img_reloaded.info):
67
+ target_mode = 'RGBA'
68
+
69
+ img_converted = img_reloaded
70
+ if img_reloaded.mode != target_mode and img_reloaded.mode not in ['RGB', 'RGBA', 'L', 'CMYK', 'LAB']:
71
+ st.caption(
72
+ f"提示: 将图像 '{original_filename}' 从 {img_reloaded.mode} 转换为 {target_mode} 以便用PNG保存。")
73
+ img_converted = img_reloaded.convert(target_mode)
74
+ elif img_reloaded.mode not in ['RGB', 'RGBA']:
75
+ img_converted = img_reloaded.convert(target_mode)
76
+
77
+ output_image_bytes = io.BytesIO()
78
+ png_params = {"icc_profile": icc_profile_bytes}
79
+ img_converted.save(output_image_bytes, format="PNG", **png_params)
80
+ img_format = "PNG"
81
+
82
+ output_image_bytes.seek(0)
83
+ return output_image_bytes, img_format
84
+
85
+ except UnidentifiedImageError:
86
+ st.error(f"无法识别文件 '{original_filename}' 为有效图片。")
87
+ return None, None
88
+ except Exception as e:
89
+ st.error(f"处理图片 '{original_filename}' 时发生错误: {e}")
90
+ return None, None
91
+
92
+
93
+ # --- Streamlit 应用界面 ---
94
+ st.set_page_config(page_title="发光青蛙表情包 HDR 制作")
95
+ st.title("🐸 发光青蛙表情包 HDR 制作")
96
+
97
+ # ICC 文件选择 (使用单选按钮)
98
+ st.markdown("#### 1. 选择要嵌入的 ICC 配置文件:")
99
+ selected_icc_display_name = st.radio(
100
+ label="选择 ICC Profile:", # label 可以设为空字符串如果你觉得上面的 markdown 标题足够
101
+ options=list(ICC_PROFILE_OPTIONS.keys()),
102
+ index=list(ICC_PROFILE_OPTIONS.keys()).index(DEFAULT_ICC_KEY), # 设置默认选项
103
+ key="icc_radio_selector",
104
+ horizontal=True, # 让单选按钮水平排列
105
+ label_visibility="collapsed" # 如果上面的 markdown 标题已足够,可以隐藏 radio 的 label
106
+ )
107
+ # 根据用户选择的显示名称获取实际的文件名
108
+ SELECTED_ICC_FILENAME = ICC_PROFILE_OPTIONS[selected_icc_display_name]
109
+ icc_profile_base_name = SELECTED_ICC_FILENAME.split('.')[0]
110
+
111
+ st.info(f"当前选定的 ICC 配置文件:**{selected_icc_display_name}**")
112
+
113
+ # 文件上传组件
114
+ st.markdown("#### 2. 上传图片文件:")
115
+ uploaded_files = st.file_uploader(
116
+ "请上传一个或多个图片文件:",
117
+ type=SUPPORTED_FORMATS,
118
+ accept_multiple_files=True,
119
+ key="file_uploader_main",
120
+ label_visibility="collapsed" # 隐藏 label,因为上面的 markdown 标题已足够
121
+ )
122
+
123
+ if uploaded_files:
124
+ icc_profile_data = get_icc_profile_bytes(SELECTED_ICC_FILENAME)
125
+
126
+ if icc_profile_data is None:
127
+ st.error(f"无法加载选定的 ICC 配置文件 '{SELECTED_ICC_FILENAME}'。请检查文件是否存在。处理已中止。")
128
+ st.stop()
129
+
130
+ processed_files_info = []
131
+
132
+ with st.status(f"正在使用 '{selected_icc_display_name}' 处理图片...", expanded=True) as status_container:
133
+ for i, uploaded_file in enumerate(uploaded_files):
134
+ status_container.write(f"⏳ 处理: {uploaded_file.name} ({i + 1}/{len(uploaded_files)})")
135
+ image_bytes = uploaded_file.getvalue()
136
+
137
+ processed_image_bytes, processed_format = embed_icc_profile(image_bytes, icc_profile_data,
138
+ uploaded_file.name)
139
+
140
+ if processed_image_bytes:
141
+ base, ext = os.path.splitext(uploaded_file.name)
142
+ actual_ext = f".{processed_format.lower()}" if processed_format else ext
143
+ new_filename = f"{base}_{icc_profile_base_name}_embedded{actual_ext}"
144
+ mime_type = f"image/{processed_format.lower()}" if processed_format else uploaded_file.type
145
+
146
+ processed_files_info.append({
147
+ "name": new_filename,
148
+ "data": processed_image_bytes,
149
+ "mime": mime_type,
150
+ "original_name": uploaded_file.name,
151
+ "id": i
152
+ })
153
+ status_container.write(f"✅ 完成: {new_filename}")
154
+ else:
155
+ status_container.write(f"❌ 失败: {uploaded_file.name}")
156
+ status_container.update(label="所有文件处理完毕!", state="complete", expanded=False)
157
+
158
+ if processed_files_info:
159
+ st.markdown("#### 3. 处理结果:")
160
+
161
+ tab_labels = [item["name"] for item in processed_files_info]
162
+
163
+ if not tab_labels:
164
+ st.info("没有成功处理的图片可供展示。")
165
+ else:
166
+ image_tabs = st.tabs(tab_labels)
167
+
168
+ for i, item in enumerate(processed_files_info):
169
+ with image_tabs[i]:
170
+ st.image(item["data"], use_container_width=True)
171
+
172
+ download_data_buffer = io.BytesIO(item["data"].getvalue())
173
+ download_data_buffer.seek(0)
174
+
175
+ st.download_button(
176
+ label=f"下载 {item['name']}",
177
+ data=download_data_buffer,
178
+ file_name=item["name"],
179
+ mime=item["mime"],
180
+ key=f"download_tab_{item['original_name']}_{item['id']}_{icc_profile_base_name}"
181
+ )
182
+ else:
183
+ if uploaded_files:
184
+ st.warning("所有上传的文件都未能成功处理。")
185
+
186
+ if len(processed_files_info) > 1:
187
+ st.markdown("#### 下载所有处理过的文件 (.zip)")
188
+
189
+ zip_buffer = io.BytesIO()
190
+ with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zf:
191
+ for item in processed_files_info:
192
+ item_data_for_zip = io.BytesIO(item["data"].getvalue())
193
+ item_data_for_zip.seek(0)
194
+ zf.writestr(item["name"], item_data_for_zip.read())
195
+
196
+ zip_buffer.seek(0)
197
+ st.download_button(
198
+ label=f"下载全部 ({len(processed_files_info)} 张图片) .zip",
199
+ data=zip_buffer,
200
+ file_name=f"processed_images_{icc_profile_base_name}_batch.zip",
201
+ mime="application/zip",
202
+ key=f"download_all_zip_{icc_profile_base_name}"
203
+ )
204
+
205
+ elif not uploaded_files:
206
+ st.info("请选择 ICC 配置文件并上传图片文件开始处理。")
qingwa.icc ADDED
Binary file (13.3 kB). View file
 
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ streamlit
2
+ Pillow