Ethscriptions commited on
Commit
70694c9
·
verified ·
1 Parent(s): f4cf8fb

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +248 -0
app.py ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import streamlit as st
3
+ import matplotlib.pyplot as plt
4
+ import matplotlib.font_manager as font_manager
5
+ import io
6
+ import base64
7
+ import os
8
+ from datetime import datetime, timedelta
9
+ import math
10
+ from matplotlib.patches import FancyBboxPatch
11
+ from pypinyin import lazy_pinyin, Style
12
+ from matplotlib.backends.backend_pdf import PdfPages
13
+
14
+ def get_font(size=14):
15
+ """Loads a specific font file, falling back to a common name if not found."""
16
+ font_path = "simHei.ttc"
17
+ if not os.path.exists(font_path):
18
+ font_path = "SimHei.ttf"
19
+ return font_manager.FontProperties(fname=font_path, size=size)
20
+
21
+ def get_pinyin_abbr(text):
22
+ """Gets the first letter of the Pinyin for the first two Chinese characters of a text."""
23
+ if not text:
24
+ return ""
25
+ # Extract the first two Chinese characters
26
+ chars = [c for c in text if '\u4e00' <= c <= '\u9fff']
27
+ chars = chars[:2]
28
+ # Get the first letter of the pinyin for each character
29
+ pinyin_list = lazy_pinyin(chars, style=Style.FIRST_LETTER)
30
+ return ''.join(pinyin_list).upper()
31
+
32
+ def process_schedule(file):
33
+ """Processes the uploaded Excel file to extract and clean the movie schedule."""
34
+ try:
35
+ # Attempt to read the date from a specific cell
36
+ date_df = pd.read_excel(file, header=None, skiprows=7, nrows=1, usecols=[3])
37
+ date_str = pd.to_datetime(date_df.iloc[0, 0]).strftime('%Y-%m-%d')
38
+ base_date = pd.to_datetime(date_str).date()
39
+ except Exception:
40
+ # Fallback to the current date if reading fails
41
+ date_str = datetime.today().strftime('%Y-%m-%d')
42
+ base_date = datetime.today().date()
43
+
44
+ try:
45
+ # Read the main schedule data
46
+ df = pd.read_excel(file, header=9, usecols=[1, 2, 4, 5])
47
+ df.columns = ['Hall', 'StartTime', 'EndTime', 'Movie']
48
+
49
+ # Clean and format the data
50
+ df['Hall'] = df['Hall'].ffill()
51
+ df.dropna(subset=['StartTime', 'EndTime', 'Movie'], inplace=True)
52
+ df['Hall'] = df['Hall'].astype(str).str.extract(r'(\d+号)')
53
+
54
+ # Convert time strings to datetime objects
55
+ df['StartTime_dt'] = pd.to_datetime(df['StartTime'], format='%H:%M', errors='coerce').apply(
56
+ lambda t: t.replace(year=base_date.year, month=base_date.month, day=base_date.day) if pd.notnull(t) else t
57
+ )
58
+ df['EndTime_dt'] = pd.to_datetime(df['EndTime'], format='%H:%M', errors='coerce').apply(
59
+ lambda t: t.replace(year=base_date.year, month=base_date.month, day=base_date.day) if pd.notnull(t) else t
60
+ )
61
+
62
+ # Handle overnight screenings
63
+ df.loc[df['EndTime_dt'] < df['StartTime_dt'], 'EndTime_dt'] += timedelta(days=1)
64
+ df = df.sort_values(['Hall', 'StartTime_dt'])
65
+
66
+ # Merge consecutive screenings of the same movie
67
+ merged_rows = []
68
+ for hall, group in df.groupby('Hall'):
69
+ group = group.sort_values('StartTime_dt')
70
+ current = None
71
+ for _, row in group.iterrows():
72
+ if current is None:
73
+ current = row.copy()
74
+ else:
75
+ if row['Movie'] == current['Movie']:
76
+ current['EndTime_dt'] = row['EndTime_dt'] # Extend the end time
77
+ else:
78
+ merged_rows.append(current)
79
+ current = row.copy()
80
+ if current is not None:
81
+ merged_rows.append(current)
82
+
83
+ merged_df = pd.DataFrame(merged_rows)
84
+
85
+ # Adjust times as per requirement
86
+ merged_df['StartTime_dt'] = merged_df['StartTime_dt'] - timedelta(minutes=10)
87
+ merged_df['EndTime_dt'] = merged_df['EndTime_dt'] - timedelta(minutes=5)
88
+
89
+ # Format times back to strings for display
90
+ merged_df['StartTime_str'] = merged_df['StartTime_dt'].dt.strftime('%H:%M')
91
+ merged_df['EndTime_str'] = merged_df['EndTime_dt'].dt.strftime('%H:%M')
92
+
93
+ return merged_df[['Hall', 'Movie', 'StartTime_str', 'EndTime_str']], date_str
94
+ except Exception:
95
+ # Return None if any processing error occurs
96
+ return None, date_str
97
+
98
+ def create_print_layout(data, date_str):
99
+ """Creates PNG and PDF layouts of the schedule, maximizing content size."""
100
+ if data is None or data.empty:
101
+ return None
102
+
103
+ # --- Figure Setup ---
104
+ # A4 paper size in inches, 300 dpi for good quality
105
+ fig_width, fig_height = 8.27, 11.69
106
+ dpi = 300
107
+
108
+ # Create separate figures for PNG and PDF
109
+ png_fig = plt.figure(figsize=(fig_width, fig_height), dpi=dpi)
110
+ png_ax = png_fig.add_subplot(111)
111
+ png_ax.set_axis_off()
112
+
113
+ pdf_fig = plt.figure(figsize=(fig_width, fig_height), dpi=dpi)
114
+ pdf_ax = pdf_fig.add_subplot(111)
115
+ pdf_ax.set_axis_off()
116
+
117
+ # Set tight margins to maximize the content area on the page.
118
+ margin = 0.03
119
+ png_fig.subplots_adjust(left=margin, right=1-margin, top=1-margin, bottom=margin)
120
+ pdf_fig.subplots_adjust(left=margin, right=1-margin, top=1-margin, bottom=margin)
121
+
122
+ # --- Drawing Logic ---
123
+ def process_figure(fig, ax):
124
+ # --- Dynamic Sizing ---
125
+ halls = sorted(data['Hall'].unique(), key=lambda h: int(h.replace('号','')) if h else 0)
126
+
127
+ # Calculate total lines needed: one for each movie + a gap between each hall's schedule
128
+ num_movie_lines = len(data)
129
+ num_hall_breaks = len(halls) - 1
130
+ total_lines = num_movie_lines + num_hall_breaks
131
+ if total_lines == 0: return
132
+
133
+ # Calculate the height available for content based on margins
134
+ available_height = 1 - (2 * margin)
135
+ # Calculate the height for each line to perfectly fill the page
136
+ line_height = available_height / total_lines
137
+
138
+ # Dynamically set font size based on line height.
139
+ # This makes the text as large as possible while still fitting.
140
+ # (72 points per inch, 0.65 is a factor for vertical padding)
141
+ font_size_pt = line_height * fig_height * 72 * 0.65
142
+ hall_font = get_font(font_size_pt)
143
+ movie_font = get_font(font_size_pt)
144
+ date_font = get_font(font_size_pt * 0.7)
145
+
146
+ # --- Content Drawing ---
147
+ ax.text(0.01, 0.99, date_str,
148
+ fontsize=date_font.get_size(), color='#A9A9A9',
149
+ ha='left', va='top', fontproperties=date_font,
150
+ transform=ax.transAxes, zorder=2)
151
+
152
+ y_position = 0.99 # Start drawing from the top
153
+
154
+ for hall in halls:
155
+ hall_data = data[data['Hall'] == hall].sort_values('StartTime_str')
156
+ y_block_top = y_position
157
+ hall_num_str = hall.replace("号", "")
158
+ hall_display_text = f"${hall_num_str}^{{\\#}}$"
159
+
160
+ # Center text vertically within its line height for better appearance
161
+ text_v_align_offset = line_height * 0.5
162
+
163
+ for i, (_, row) in enumerate(hall_data.iterrows()):
164
+ if i == 0:
165
+ ax.text(0.05, y_position - text_v_align_offset, hall_display_text,
166
+ fontproperties=hall_font, fontweight='bold',
167
+ ha='center', va='center', transform=ax.transAxes, zorder=2)
168
+
169
+ pinyin_abbr = get_pinyin_abbr(row['Movie'])
170
+ movie_display_text = f"{i+1}. {pinyin_abbr} {row['Movie']}"
171
+ ax.text(0.15, y_position - text_v_align_offset, movie_display_text,
172
+ fontproperties=movie_font, ha='left', va='center',
173
+ transform=ax.transAxes, zorder=2, clip_on=True)
174
+
175
+ time_display_text = f"{row['StartTime_str']} - {row['EndTime_str']}"
176
+ ax.text(0.99, y_position - text_v_align_offset, time_display_text,
177
+ fontproperties=movie_font, ha='right', va='center',
178
+ transform=ax.transAxes, zorder=2)
179
+
180
+ y_position -= line_height
181
+
182
+ # Add a decorative rounded box around each hall's schedule
183
+ y_block_bottom = y_position + line_height
184
+ rect_height = y_block_top - y_block_bottom
185
+
186
+ rect = FancyBboxPatch((0, y_block_bottom), 1, rect_height,
187
+ boxstyle="round,pad=0.01,rounding_size=0.02",
188
+ edgecolor='gray', facecolor='white',
189
+ linewidth=0.8, zorder=1, transform=ax.transAxes)
190
+ ax.add_patch(rect)
191
+
192
+ if hall != halls[-1]:
193
+ y_position -= line_height # Add gap for the next block
194
+
195
+ # Render the layout on both figures
196
+ process_figure(png_fig, png_ax)
197
+ process_figure(pdf_fig, pdf_ax)
198
+
199
+ # --- File Saving ---
200
+ # Save PNG to a memory buffer
201
+ png_buffer = io.BytesIO()
202
+ png_fig.savefig(png_buffer, format='png', bbox_inches='tight', pad_inches=0.05)
203
+ png_buffer.seek(0)
204
+ image_base64 = base64.b64encode(png_buffer.getvalue()).decode()
205
+ plt.close(png_fig)
206
+
207
+ # Save PDF to a memory buffer
208
+ pdf_buffer = io.BytesIO()
209
+ with PdfPages(pdf_buffer) as pdf:
210
+ pdf.savefig(pdf_fig, bbox_inches='tight', pad_inches=0.05)
211
+ pdf_buffer.seek(0)
212
+ pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode()
213
+ plt.close(pdf_fig)
214
+
215
+ return {
216
+ 'png': f"data:image/png;base64,{image_base64}",
217
+ 'pdf': f"data:application/pdf;base64,{pdf_base64}"
218
+ }
219
+
220
+ def display_pdf(base64_pdf):
221
+ """Generates the HTML to embed and display a PDF in Streamlit."""
222
+ pdf_display = f"""
223
+ <iframe src="{base64_pdf}" width="100%" height="800" type="application/pdf"></iframe>
224
+ """
225
+ return pdf_display
226
+
227
+ # --- Streamlit App ---
228
+ st.set_page_config(page_title="LED 屏幕时间表打印", layout="wide")
229
+ st.title("LED 屏幕时间表打印")
230
+
231
+ uploaded_file = st.file_uploader("选择打开【放映时间核对表.xls】文件", accept_multiple_files=False, type=["xls"])
232
+
233
+ if uploaded_file:
234
+ with st.spinner("文件正在处理中,请稍候..."):
235
+ schedule, date_str = process_schedule(uploaded_file)
236
+ if schedule is not None:
237
+ output = create_print_layout(schedule, date_str)
238
+
239
+ # Use tabs for PDF and PNG previews
240
+ tab1, tab2 = st.tabs(["PDF 预览", "PNG 预览"])
241
+
242
+ with tab1:
243
+ st.markdown(display_pdf(output['pdf']), unsafe_allow_html=True)
244
+
245
+ with tab2:
246
+ st.image(output['png'], use_container_width=True)
247
+ else:
248
+ st.error("无法处理文件,请检查文件格式或内容是否正确。")