如何通过Dash/Streamlit/Flask在客户端提取图片EXIF(不上传服务器)
解决方案:客户端提取图片EXIF并传回Python
一、修复你的Dash代码
你的Dash代码无响应主要是ID不匹配、数据处理逻辑错误、EXIF调用方式不对导致的,以下是修复后的完整代码:
import dash from dash import dcc, html, Input, Output app = dash.Dash(__name__, external_scripts=['https://cdn.jsdelivr.net/npm/exif-js']) app.title = "Get EXIF Data" app.layout = html.Div( [ dcc.Upload( id='upload-images', children=html.Div([ 'Drag and Drop or ', html.A('Select Files') ]), style={ 'width': '100%', 'height': '60px', 'lineHeight': '60px', 'borderWidth': '1px', 'borderStyle': 'dashed', 'borderRadius': '5px', 'textAlign': 'center', 'margin': '10px' }, multiple=True ), html.Div(id='display-exif'), # 隐藏存储组件,用于将EXIF数据传回Python端 dcc.Store(id='exif-data-store') ] ) # 客户端回调:提取EXIF并返回显示内容+存储数据 app.clientside_callback( """ function(contents) { if (!contents || contents.length === 0) { return ["", null]; } // 处理第一个上传的文件(base64转Blob) const content = contents[0]; const [typePart, base64Data] = content.split(','); const contentType = typePart.split(':')[1].split(';')[0]; const blob = b64toBlob(base64Data, contentType); // 创建图片元素供EXIF解析 const img = new Image(); img.src = URL.createObjectURL(blob); return new Promise((resolve) => { img.onload = function() { EXIF.getData(img, function() { const exifData = { make: EXIF.getTag(this, "Make") || "Unknown", model: EXIF.getTag(this, "Model") || "Unknown", dateTime: EXIF.getTag(this, "DateTimeOriginal") || "Unknown", gpsLat: EXIF.getTag(this, "GPSLatitude") || "Unknown" }; // 返回显示内容和存储用的JSON数据 resolve([ html.Div([ html.H4("EXIF信息"), html.P(`相机品牌: ${exifData.make}`), html.P(`相机型号: ${exifData.model}`), html.P(`拍摄时间: ${exifData.dateTime}`) ]), exifData ]); }); }; }); // 辅助函数:base64转Blob function b64toBlob(b64Data, contentType = '', sliceSize = 512) { const byteCharacters = atob(b64Data); const byteArrays = []; for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) { const slice = byteCharacters.slice(offset, offset + sliceSize); const byteNumbers = new Array(slice.length); for (let i = 0; i < slice.length; i++) { byteNumbers[i] = slice.charCodeAt(i); } byteArrays.push(new Uint8Array(byteNumbers)); } return new Blob(byteArrays, { type: contentType }); } } """, [Output('display-exif', 'children'), Output('exif-data-store', 'data')], Input('upload-images', 'contents'), prevent_initial_call=True ) # Python端回调:接收EXIF数据做后续处理 @app.callback( Output('display-exif', 'children', allow_duplicate=True), Input('exif-data-store', 'data'), prevent_initial_call=True ) def process_exif_data(exif_data): # 这里可添加自定义处理逻辑,比如存入数据库、数据分析等 processed_note = f"Python端已完成EXIF数据处理:品牌{exif_data['make']},型号{exif_data['model']}" return html.Div([ html.H4("EXIF信息"), html.P(f"相机品牌: {exif_data['make']}"), html.P(f"相机型号: {exif_data['model']}"), html.P(f"拍摄时间: {exif_data['dateTime']}"), html.Hr(), html.P(processed_note) ]) if __name__ == '__main__': app.run_server(debug=False)
关键修复点:
- 修正了上传组件与回调的ID匹配问题
- 正确将
dcc.Upload返回的base64内容转为Blob,再创建图片供EXIF.js解析 - 用Promise处理异步加载逻辑,确保EXIF数据提取完成后再返回结果
- 通过
dcc.Store将EXIF数据以JSON格式传回Python端 - 新增Python端回调,接收数据并执行后续处理
二、Streamlit实现方案
Streamlit默认会上传文件到服务器,但可通过嵌入JS实现客户端EXIF提取,再将数据传回Python:
import streamlit as st import json st.title("客户端提取图片EXIF") # 嵌入JS实现客户端EXIF提取 st.components.v1.html(""" <script src="https://cdn.jsdelivr.net/npm/exif-js"></script> <div> <input type="file" id="image-upload" accept="image/*"> <div id="exif-display"></div> </div> <script> const uploadInput = document.getElementById('image-upload'); const displayDiv = document.getElementById('exif-display'); uploadInput.addEventListener('change', function(e) { const file = e.target.files[0]; if (!file) return; const img = new Image(); img.src = URL.createObjectURL(file); img.onload = function() { EXIF.getData(img, function() { const exifData = { make: EXIF.getTag(this, "Make") || "Unknown", model: EXIF.getTag(this, "Model") || "Unknown", dateTime: EXIF.getTag(this, "DateTimeOriginal") || "Unknown" }; // 显示客户端提取结果 displayDiv.innerHTML = ` <h4>EXIF信息</h4> <p>相机品牌: ${exifData.make}</p> <p>相机型号: ${exifData.model}</p> <p>拍摄时间: ${exifData.dateTime}</p> `; // 将数据传回Streamlit window.parent.postMessage({type: 'EXIF_DATA', data: exifData}, '*'); }); }; }); </script> """, height=300) # 接收JS传回的EXIF数据 st.components.v1.html(""" <script> window.addEventListener('message', function(event) { if (event.data.type === 'EXIF_DATA') { // 创建隐藏文本框存储数据,供Python读取 const textarea = document.createElement('textarea'); textarea.id = 'exif-data'; textarea.style.display = 'none'; textarea.value = JSON.stringify(event.data.data); document.body.appendChild(textarea); // 触发Streamlit组件更新 window.parent.document.dispatchEvent(new Event('streamlit:componentValueChanged')); } }); </script> """, height=0) # 读取隐藏文本框的EXIF数据 exif_raw = st.text_input("", key='exif_input', label_visibility='hidden') if exif_raw: exif_data = json.loads(exif_raw) st.subheader("Python端处理结果") st.write(f"相机品牌: {exif_data['make']}") st.write(f"相机型号: {exif_data['model']}") st.write(f"拍摄时间: {exif_data['dateTime']}") # 此处添加自定义Python处理逻辑
说明:
- 通过
st.components.v1.html嵌入JS,在浏览器端直接完成EXIF提取 - 用
postMessage将数据传到Streamlit父页面,存入隐藏文本框供Python读取 - 全程无需上传图片文件到服务器,完全在客户端完成解析
三、核心逻辑总结
不管用Dash还是Streamlit,实现需求的核心步骤都是:
- 在客户端加载EXIF.js库
- 将用户上传的图片转为可解析的图片元素
- 提取目标EXIF字段并转为JSON格式
- 通过组件(Dash的dcc.Store/Streamlit的隐藏输入框)将数据传回Python
- 在Python端接收数据并执行后续处理
内容的提问来源于stack exchange,提问作者b.dot
相关产品推荐
相关产品推荐

