Ethscriptions commited on
Commit
a215102
·
verified ·
1 Parent(s): edb1602

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +275 -54
app.py CHANGED
@@ -9,13 +9,30 @@ from datetime import datetime, timedelta
9
  import math
10
  from pypinyin import lazy_pinyin, Style
11
  from matplotlib.backends.backend_pdf import PdfPages
 
 
12
 
 
 
 
 
 
 
 
 
13
  def get_font(size=14):
14
  """Loads a specific font file, falling back to a default if not found."""
15
  font_path = "simHei.ttc"
16
  if not os.path.exists(font_path):
17
- font_path = "SimHei.ttf" # Fallback font
18
- return font_manager.FontProperties(fname=font_path, size=size)
 
 
 
 
 
 
 
19
 
20
  def get_pinyin_abbr(text):
21
  """Gets the first letter of the Pinyin for the first two Chinese characters of a text."""
@@ -28,8 +45,9 @@ def get_pinyin_abbr(text):
28
  pinyin_list = lazy_pinyin(chars, style=Style.FIRST_LETTER)
29
  return ''.join(pinyin_list).upper()
30
 
31
- def process_schedule(file):
32
- """Processes the uploaded Excel file to extract and clean movie schedule data."""
 
33
  try:
34
  # Attempt to read the date from a specific cell
35
  date_df = pd.read_excel(file, header=None, skiprows=7, nrows=1, usecols=[3])
@@ -39,14 +57,14 @@ def process_schedule(file):
39
  # Fallback to the current date if reading fails
40
  date_str = datetime.today().strftime('%Y-%m-%d')
41
  base_date = datetime.today().date()
42
-
43
  try:
44
  df = pd.read_excel(file, header=9, usecols=[1, 2, 4, 5])
45
  df.columns = ['Hall', 'StartTime', 'EndTime', 'Movie']
46
  df['Hall'] = df['Hall'].ffill()
47
  df.dropna(subset=['StartTime', 'EndTime', 'Movie'], inplace=True)
48
  df['Hall'] = df['Hall'].astype(str).str.extract(r'(\d+号)')
49
-
50
  # Convert times to datetime objects, handling overnight screenings
51
  df['StartTime_dt'] = pd.to_datetime(df['StartTime'], format='%H:%M', errors='coerce').apply(
52
  lambda t: t.replace(year=base_date.year, month=base_date.month, day=base_date.day) if pd.notnull(t) else t
@@ -56,7 +74,7 @@ def process_schedule(file):
56
  )
57
  df.loc[df['EndTime_dt'] < df['StartTime_dt'], 'EndTime_dt'] += timedelta(days=1)
58
  df = df.sort_values(['Hall', 'StartTime_dt'])
59
-
60
  # Merge consecutive screenings of the same movie
61
  merged_rows = []
62
  for hall, group in df.groupby('Hall'):
@@ -73,23 +91,24 @@ def process_schedule(file):
73
  current = row.copy()
74
  if current is not None:
75
  merged_rows.append(current)
76
-
77
  merged_df = pd.DataFrame(merged_rows)
78
-
79
  # Adjust start and end times
80
  merged_df['StartTime_dt'] = merged_df['StartTime_dt'] - timedelta(minutes=10)
81
  merged_df['EndTime_dt'] = merged_df['EndTime_dt'] - timedelta(minutes=5)
82
-
83
  merged_df['StartTime_str'] = merged_df['StartTime_dt'].dt.strftime('%H:%M')
84
  merged_df['EndTime_str'] = merged_df['EndTime_dt'].dt.strftime('%H:%M')
85
-
86
  return merged_df[['Hall', 'Movie', 'StartTime_str', 'EndTime_str']], date_str
87
  except Exception as e:
88
  st.error(f"An error occurred during file processing: {e}")
89
  return None, date_str
90
 
91
- def create_print_layout(data, date_str):
92
- """Generates PNG and PDF layouts of the schedule based on the provided data."""
 
93
  if data is None or data.empty:
94
  return None
95
 
@@ -98,97 +117,79 @@ def create_print_layout(data, date_str):
98
  png_ax = png_fig.add_subplot(111)
99
  png_ax.set_axis_off()
100
  png_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
101
-
102
  pdf_fig = plt.figure(figsize=(8.27, 11.69), dpi=300)
103
  pdf_ax = pdf_fig.add_subplot(111)
104
  pdf_ax.set_axis_off()
105
  pdf_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
106
-
107
  def process_figure(fig, ax):
108
  halls = sorted(data['Hall'].unique(), key=lambda h: int(h.replace('号','')) if h else 0)
109
 
110
- # 1. A4 as a table: Calculate row height based on content
111
  num_separators = len(halls) - 1
112
- # Each movie, separator, and top/bottom margin gets a row
113
  total_layout_rows = len(data) + num_separators + 2
114
 
115
- available_height = 0.96 # Effective printable area (1.0 - top_margin - bottom_margin)
116
  row_height = available_height / total_layout_rows
117
 
118
- # Font height is 90% of the row height
119
  fig_height_inches = fig.get_figheight()
120
- row_height_points = row_height * fig_height_inches * 72 # Convert figure coords to points
121
  font_size = row_height_points * 0.9
122
 
123
- # Define fonts
124
  date_font = get_font(font_size * 0.8)
125
  hall_font = get_font(font_size)
126
  movie_font = get_font(font_size)
127
 
128
- # Define X positions for columns
129
- # MODIFICATION: Hall number is now at the far left (x=0.0)
130
  col_hall_left = 0.0
131
  col_movie_right = 0.50
132
  col_seq_left = 0.52
133
  col_pinyin_left = 0.62
134
  col_time_left = 0.75
135
 
136
- # Display date at the top left, aligned with the hall number
137
  ax.text(col_hall_left, 0.99, date_str, color='#A9A9A9',
138
  ha='left', va='top', fontproperties=date_font, transform=ax.transAxes)
139
 
140
- # Set starting Y position after top margin
141
  y_position = 0.98 - row_height
142
 
143
  for i, hall in enumerate(halls):
144
  hall_data = data[data['Hall'] == hall]
145
 
146
- # Add a separator line between halls (not before the first one)
147
  if i > 0:
148
- # MODIFICATION: Line starts at the far left, aligned with the hall number
149
  ax.axhline(y=y_position + row_height / 2, xmin=col_hall_left, xmax=0.97, color='black', linewidth=0.7)
150
- y_position -= row_height # Move down one row for the separator space
151
 
152
  movie_count = 1
153
  for _, row in hall_data.iterrows():
154
- # Hall number (left-aligned), shown only for the first movie in the hall
155
  if movie_count == 1:
156
  ax.text(col_hall_left, y_position, f"{hall.replace('号', '')}#",
157
  ha='left', va='center', fontweight='bold',
158
  fontproperties=hall_font, transform=ax.transAxes)
159
 
160
- # Column: Movie Name (Right-aligned)
161
  ax.text(col_movie_right, y_position, row['Movie'],
162
  ha='right', va='center', fontproperties=movie_font, transform=ax.transAxes)
163
 
164
- # Column: Sequence Number (Left-aligned)
165
  ax.text(col_seq_left, y_position, f"{movie_count}.",
166
  ha='left', va='center', fontproperties=movie_font, transform=ax.transAxes)
167
 
168
- # Column: Pinyin Abbreviation (Left-aligned)
169
  pinyin_abbr = get_pinyin_abbr(row['Movie'])
170
  ax.text(col_pinyin_left, y_position, pinyin_abbr,
171
  ha='left', va='center', fontproperties=movie_font, transform=ax.transAxes)
172
 
173
- # Column: Time (Left-aligned)
174
  ax.text(col_time_left, y_position, f"{row['StartTime_str']}-{row['EndTime_str']}",
175
  ha='left', va='center', fontproperties=movie_font, transform=ax.transAxes)
176
 
177
- y_position -= row_height # Move to the next row
178
  movie_count += 1
179
 
180
- # Process both the PNG and PDF figures with the new layout logic
181
  process_figure(png_fig, png_ax)
182
  process_figure(pdf_fig, pdf_ax)
183
 
184
- # Save PNG to a buffer
185
  png_buffer = io.BytesIO()
186
  png_fig.savefig(png_buffer, format='png', bbox_inches='tight', pad_inches=0.05)
187
  png_buffer.seek(0)
188
  image_base64 = base64.b64encode(png_buffer.getvalue()).decode()
189
  plt.close(png_fig)
190
 
191
- # Save PDF to a buffer
192
  pdf_buffer = io.BytesIO()
193
  with PdfPages(pdf_buffer) as pdf:
194
  pdf.savefig(pdf_fig, bbox_inches='tight', pad_inches=0.05)
@@ -201,6 +202,180 @@ def create_print_layout(data, date_str):
201
  'pdf': f"data:application/pdf;base64,{pdf_base64}"
202
  }
203
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  def display_pdf(base64_pdf):
205
  """Generates the HTML to embed and display a PDF in Streamlit."""
206
  pdf_display = f"""
@@ -208,25 +383,71 @@ def display_pdf(base64_pdf):
208
  """
209
  return pdf_display
210
 
211
- # --- Streamlit App ---
212
- st.set_page_config(page_title="LED 屏幕时间表打印", layout="wide")
213
- st.title("LED 屏幕时间表打印")
214
 
215
- uploaded_file = st.file_uploader("选择打开【放映时间核对表.xls】文件", accept_multiple_files=False, type=["xls"])
 
 
 
 
216
 
217
  if uploaded_file:
218
  with st.spinner("文件正在处理中,请稍候..."):
219
- schedule, date_str = process_schedule(uploaded_file)
220
- if schedule is not None:
221
- output = create_print_layout(schedule, date_str)
222
-
223
- # Create tabs for PDF and PNG previews
224
- tab1, tab2 = st.tabs(["PDF 预览", "PNG 预览"])
225
-
226
- with tab1:
227
- st.markdown(display_pdf(output['pdf']), unsafe_allow_html=True)
228
-
229
- with tab2:
230
- st.image(output['png'], use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  else:
232
- st.error("无法处理文件,请检查文件格式或内容是否正确。")
 
9
  import math
10
  from pypinyin import lazy_pinyin, Style
11
  from matplotlib.backends.backend_pdf import PdfPages
12
+ import matplotlib.gridspec as gridspec
13
+ from matplotlib.patches import FancyBboxPatch
14
 
15
+ # --- Constants for "Quick Print" (放映场次核对表) ---
16
+ SPLIT_TIME = "17:30"
17
+ BUSINESS_START = "09:30"
18
+ BUSINESS_END = "01:30"
19
+ BORDER_COLOR = '#A9A9A9'
20
+ DATE_COLOR = '#A9A9A9'
21
+
22
+ # --- Helper functions for "LED Screen" (放映时间核对表) ---
23
  def get_font(size=14):
24
  """Loads a specific font file, falling back to a default if not found."""
25
  font_path = "simHei.ttc"
26
  if not os.path.exists(font_path):
27
+ font_path = "SimHei.ttf" # Fallback font
28
+ # Add a final fallback for systems without Chinese fonts
29
+ try:
30
+ return font_manager.FontProperties(fname=font_path, size=size)
31
+ except RuntimeError:
32
+ # If the font file is not found, use a default font that should exist.
33
+ # This will likely not render Chinese characters correctly but prevents crashing.
34
+ return font_manager.FontProperties(family='sans-serif', size=size)
35
+
36
 
37
  def get_pinyin_abbr(text):
38
  """Gets the first letter of the Pinyin for the first two Chinese characters of a text."""
 
45
  pinyin_list = lazy_pinyin(chars, style=Style.FIRST_LETTER)
46
  return ''.join(pinyin_list).upper()
47
 
48
+ # --- Processing logic for "LED Screen" (放映时间核对表) ---
49
+ def process_schedule_led(file):
50
+ """Processes the '放映时间核对表.xls' file."""
51
  try:
52
  # Attempt to read the date from a specific cell
53
  date_df = pd.read_excel(file, header=None, skiprows=7, nrows=1, usecols=[3])
 
57
  # Fallback to the current date if reading fails
58
  date_str = datetime.today().strftime('%Y-%m-%d')
59
  base_date = datetime.today().date()
60
+
61
  try:
62
  df = pd.read_excel(file, header=9, usecols=[1, 2, 4, 5])
63
  df.columns = ['Hall', 'StartTime', 'EndTime', 'Movie']
64
  df['Hall'] = df['Hall'].ffill()
65
  df.dropna(subset=['StartTime', 'EndTime', 'Movie'], inplace=True)
66
  df['Hall'] = df['Hall'].astype(str).str.extract(r'(\d+号)')
67
+
68
  # Convert times to datetime objects, handling overnight screenings
69
  df['StartTime_dt'] = pd.to_datetime(df['StartTime'], format='%H:%M', errors='coerce').apply(
70
  lambda t: t.replace(year=base_date.year, month=base_date.month, day=base_date.day) if pd.notnull(t) else t
 
74
  )
75
  df.loc[df['EndTime_dt'] < df['StartTime_dt'], 'EndTime_dt'] += timedelta(days=1)
76
  df = df.sort_values(['Hall', 'StartTime_dt'])
77
+
78
  # Merge consecutive screenings of the same movie
79
  merged_rows = []
80
  for hall, group in df.groupby('Hall'):
 
91
  current = row.copy()
92
  if current is not None:
93
  merged_rows.append(current)
94
+
95
  merged_df = pd.DataFrame(merged_rows)
96
+
97
  # Adjust start and end times
98
  merged_df['StartTime_dt'] = merged_df['StartTime_dt'] - timedelta(minutes=10)
99
  merged_df['EndTime_dt'] = merged_df['EndTime_dt'] - timedelta(minutes=5)
100
+
101
  merged_df['StartTime_str'] = merged_df['StartTime_dt'].dt.strftime('%H:%M')
102
  merged_df['EndTime_str'] = merged_df['EndTime_dt'].dt.strftime('%H:%M')
103
+
104
  return merged_df[['Hall', 'Movie', 'StartTime_str', 'EndTime_str']], date_str
105
  except Exception as e:
106
  st.error(f"An error occurred during file processing: {e}")
107
  return None, date_str
108
 
109
+ # --- Layout generation for "LED Screen" (放映时间核对表) ---
110
+ def create_print_layout_led(data, date_str):
111
+ """Generates PNG and PDF layouts for the 'LED Screen' schedule."""
112
  if data is None or data.empty:
113
  return None
114
 
 
117
  png_ax = png_fig.add_subplot(111)
118
  png_ax.set_axis_off()
119
  png_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
120
+
121
  pdf_fig = plt.figure(figsize=(8.27, 11.69), dpi=300)
122
  pdf_ax = pdf_fig.add_subplot(111)
123
  pdf_ax.set_axis_off()
124
  pdf_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
125
+
126
  def process_figure(fig, ax):
127
  halls = sorted(data['Hall'].unique(), key=lambda h: int(h.replace('号','')) if h else 0)
128
 
 
129
  num_separators = len(halls) - 1
 
130
  total_layout_rows = len(data) + num_separators + 2
131
 
132
+ available_height = 0.96
133
  row_height = available_height / total_layout_rows
134
 
 
135
  fig_height_inches = fig.get_figheight()
136
+ row_height_points = row_height * fig_height_inches * 72
137
  font_size = row_height_points * 0.9
138
 
 
139
  date_font = get_font(font_size * 0.8)
140
  hall_font = get_font(font_size)
141
  movie_font = get_font(font_size)
142
 
 
 
143
  col_hall_left = 0.0
144
  col_movie_right = 0.50
145
  col_seq_left = 0.52
146
  col_pinyin_left = 0.62
147
  col_time_left = 0.75
148
 
 
149
  ax.text(col_hall_left, 0.99, date_str, color='#A9A9A9',
150
  ha='left', va='top', fontproperties=date_font, transform=ax.transAxes)
151
 
 
152
  y_position = 0.98 - row_height
153
 
154
  for i, hall in enumerate(halls):
155
  hall_data = data[data['Hall'] == hall]
156
 
 
157
  if i > 0:
 
158
  ax.axhline(y=y_position + row_height / 2, xmin=col_hall_left, xmax=0.97, color='black', linewidth=0.7)
159
+ y_position -= row_height
160
 
161
  movie_count = 1
162
  for _, row in hall_data.iterrows():
 
163
  if movie_count == 1:
164
  ax.text(col_hall_left, y_position, f"{hall.replace('号', '')}#",
165
  ha='left', va='center', fontweight='bold',
166
  fontproperties=hall_font, transform=ax.transAxes)
167
 
 
168
  ax.text(col_movie_right, y_position, row['Movie'],
169
  ha='right', va='center', fontproperties=movie_font, transform=ax.transAxes)
170
 
 
171
  ax.text(col_seq_left, y_position, f"{movie_count}.",
172
  ha='left', va='center', fontproperties=movie_font, transform=ax.transAxes)
173
 
 
174
  pinyin_abbr = get_pinyin_abbr(row['Movie'])
175
  ax.text(col_pinyin_left, y_position, pinyin_abbr,
176
  ha='left', va='center', fontproperties=movie_font, transform=ax.transAxes)
177
 
 
178
  ax.text(col_time_left, y_position, f"{row['StartTime_str']}-{row['EndTime_str']}",
179
  ha='left', va='center', fontproperties=movie_font, transform=ax.transAxes)
180
 
181
+ y_position -= row_height
182
  movie_count += 1
183
 
 
184
  process_figure(png_fig, png_ax)
185
  process_figure(pdf_fig, pdf_ax)
186
 
 
187
  png_buffer = io.BytesIO()
188
  png_fig.savefig(png_buffer, format='png', bbox_inches='tight', pad_inches=0.05)
189
  png_buffer.seek(0)
190
  image_base64 = base64.b64encode(png_buffer.getvalue()).decode()
191
  plt.close(png_fig)
192
 
 
193
  pdf_buffer = io.BytesIO()
194
  with PdfPages(pdf_buffer) as pdf:
195
  pdf.savefig(pdf_fig, bbox_inches='tight', pad_inches=0.05)
 
202
  'pdf': f"data:application/pdf;base64,{pdf_base64}"
203
  }
204
 
205
+ # --- Processing logic for "Quick Print" (放映场次核对表) ---
206
+ def process_schedule_quick(file):
207
+ """Processes the '放映场次核对表.xls' file."""
208
+ try:
209
+ df = pd.read_excel(file, skiprows=8)
210
+ df = df.iloc[:, [6, 7, 9]]
211
+ df.columns = ['Hall', 'StartTime', 'EndTime']
212
+ df = df.dropna(subset=['Hall', 'StartTime', 'EndTime'])
213
+ df['Hall'] = df['Hall'].str.extract(r'(\d+)号').astype(str) + ' '
214
+
215
+ base_date = datetime.today().date()
216
+ df['StartTime'] = pd.to_datetime(df['StartTime'])
217
+ df['EndTime'] = pd.to_datetime(df['EndTime'])
218
+
219
+ business_start = datetime.strptime(f"{base_date} {BUSINESS_START}", "%Y-%m-%d %H:%M")
220
+ business_end = datetime.strptime(f"{base_date} {BUSINESS_END}", "%Y-%m-%d %H:%M")
221
+ if business_end < business_start:
222
+ business_end += timedelta(days=1)
223
+
224
+ for idx, row in df.iterrows():
225
+ end_time = row['EndTime']
226
+ if end_time.hour < 9:
227
+ df.at[idx, 'EndTime'] = end_time + timedelta(days=1)
228
+ if row['StartTime'].hour >= 21 and end_time.hour < 9:
229
+ df.at[idx, 'EndTime'] = end_time + timedelta(days=1)
230
+
231
+ df['time_for_comparison'] = df['EndTime'].apply(lambda x: datetime.combine(base_date, x.time()))
232
+ df.loc[df['time_for_comparison'].dt.hour < 9, 'time_for_comparison'] += timedelta(days=1)
233
+
234
+ valid_times = (
235
+ (df['time_for_comparison'] >= datetime.combine(base_date, business_start.time())) &
236
+ (df['time_for_comparison'] <= datetime.combine(base_date + timedelta(days=1), business_end.time()))
237
+ )
238
+ df = df[valid_times]
239
+ df = df.sort_values('EndTime')
240
+
241
+ split_time_dt = datetime.strptime(f"{base_date} {SPLIT_TIME}", "%Y-%m-%d %H:%M")
242
+
243
+ part1 = df[df['time_for_comparison'] <= split_time_dt].copy()
244
+ part2 = df[df['time_for_comparison'] > split_time_dt].copy()
245
+
246
+ for part in [part1, part2]:
247
+ part['EndTime'] = part['EndTime'].dt.strftime('%-H:%M')
248
+
249
+ date_df = pd.read_excel(file, skiprows=5, nrows=1, usecols=[2], header=None)
250
+ date_cell = date_df.iloc[0, 0]
251
+
252
+ try:
253
+ if isinstance(date_cell, str):
254
+ date_str = datetime.strptime(date_cell, '%Y-%m-%d').strftime('%Y-%m-%d')
255
+ else:
256
+ date_str = pd.to_datetime(date_cell).strftime('%Y-%m-%d')
257
+ except:
258
+ date_str = datetime.today().strftime('%Y-%m-%d')
259
+
260
+ return part1[['Hall', 'EndTime']], part2[['Hall', 'EndTime']], date_str
261
+
262
+ except Exception as e:
263
+ st.error(f"处理文件时出错: {str(e)}")
264
+ return None, None, None
265
+
266
+ # --- Layout generation for "Quick Print" (放映场次核对表) ---
267
+ def create_print_layout_quick(data, title, date_str):
268
+ """Creates print layout for the 'Quick Print' schedule."""
269
+ if data.empty:
270
+ return None
271
+
272
+ png_fig = plt.figure(figsize=(5.83, 8.27), dpi=300) # A5
273
+ png_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
274
+
275
+ pdf_fig = plt.figure(figsize=(5.83, 8.27), dpi=300) # A5
276
+ pdf_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
277
+
278
+ def process_figure(fig, is_pdf=False):
279
+ plt.rcParams['font.family'] = 'sans-serif'
280
+ plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'Heiti TC', 'SimHei']
281
+
282
+ total_items = len(data)
283
+ num_cols = 3
284
+ num_rows = math.ceil(total_items / num_cols)
285
+
286
+ gs = gridspec.GridSpec(num_rows + 1, num_cols, hspace=0.05, wspace=0.05, height_ratios=[0.1] + [1] * num_rows, figure=fig)
287
+
288
+ target_width_px = 1
289
+ if total_items > 0:
290
+ ax_temp = fig.add_subplot(gs[1, 0])
291
+ fig.canvas.draw()
292
+ target_width_px = ax_temp.get_window_extent().width * 0.90
293
+ ax_temp.remove()
294
+
295
+ available_height_per_row = (8.27 * 0.9 * (1 / 1.2)) / num_rows if num_rows > 0 else 1
296
+ date_fontsize = min(40, max(10, available_height_per_row * 72 * 0.5))
297
+
298
+ data_values = data.values.tolist()
299
+ while len(data_values) % num_cols != 0:
300
+ data_values.append(['', ''])
301
+ rows_per_col_layout = math.ceil(len(data_values) / num_cols)
302
+
303
+ sorted_data = [['', '']] * len(data_values)
304
+ for i, item in enumerate(data_values):
305
+ if item[0] and item[1]:
306
+ row_in_col = i % rows_per_col_layout
307
+ col_idx = i // rows_per_col_layout
308
+ new_index = row_in_col * num_cols + col_idx
309
+ if new_index < len(sorted_data):
310
+ sorted_data[new_index] = item
311
+
312
+ for idx, (hall, end_time) in enumerate(sorted_data):
313
+ if hall and end_time:
314
+ row_grid = idx // num_cols + 1
315
+ col_grid = idx % num_cols
316
+
317
+ if row_grid < num_rows + 1:
318
+ ax = fig.add_subplot(gs[row_grid, col_grid])
319
+ for spine in ax.spines.values():
320
+ spine.set_visible(False)
321
+
322
+ bbox = FancyBboxPatch(
323
+ (0.01, 0.01), 0.98, 0.98,
324
+ boxstyle="round,pad=0,rounding_size=0.02",
325
+ edgecolor=BORDER_COLOR, facecolor='none',
326
+ linewidth=0.5, transform=ax.transAxes, clip_on=False
327
+ )
328
+ ax.add_patch(bbox)
329
+
330
+ display_text = f"{hall}{end_time}"
331
+ t = ax.text(0.5, 0.5, display_text,
332
+ fontweight='bold', ha='center', va='center',
333
+ transform=ax.transAxes)
334
+
335
+ current_size = 120
336
+ while current_size > 1:
337
+ t.set_fontsize(current_size)
338
+ text_bbox = t.get_window_extent(renderer=fig.canvas.get_renderer())
339
+ if text_bbox.width <= target_width_px:
340
+ break
341
+ current_size -= 2
342
+
343
+ ax.set_xticks([])
344
+ ax.set_yticks([])
345
+
346
+ ax_date = fig.add_subplot(gs[0, :])
347
+ ax_date.text(0.01, 0.5, f"{date_str} {title}",
348
+ fontsize=date_fontsize * 0.5,
349
+ color=DATE_COLOR, fontweight='bold',
350
+ ha='left', va='center', transform=ax_date.transAxes)
351
+ for spine in ax_date.spines.values():
352
+ spine.set_visible(False)
353
+ ax_date.set_xticks([])
354
+ ax_date.set_yticks([])
355
+ ax_date.set_facecolor('none')
356
+
357
+ process_figure(png_fig)
358
+ process_figure(pdf_fig, is_pdf=True)
359
+
360
+ png_buffer = io.BytesIO()
361
+ png_fig.savefig(png_buffer, format='png', bbox_inches='tight', pad_inches=0.02)
362
+ png_buffer.seek(0)
363
+ png_base64 = base64.b64encode(png_buffer.getvalue()).decode()
364
+ plt.close(png_fig)
365
+
366
+ pdf_buffer = io.BytesIO()
367
+ with PdfPages(pdf_buffer) as pdf:
368
+ pdf.savefig(pdf_fig, bbox_inches='tight', pad_inches=0.02)
369
+ pdf_buffer.seek(0)
370
+ pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode()
371
+ plt.close(pdf_fig)
372
+
373
+ return {
374
+ 'png': f'data:image/png;base64,{png_base64}',
375
+ 'pdf': f'data:application/pdf;base64,{pdf_base64}'
376
+ }
377
+
378
+ # --- Generic Helper to Display PDF ---
379
  def display_pdf(base64_pdf):
380
  """Generates the HTML to embed and display a PDF in Streamlit."""
381
  pdf_display = f"""
 
383
  """
384
  return pdf_display
385
 
386
+ # --- Main Streamlit App ---
387
+ st.set_page_config(page_title="影院排期打印工具", layout="wide")
388
+ st.title("影院排期打印工具")
389
 
390
+ uploaded_file = st.file_uploader(
391
+ "选择【放映时间核对表.xls】或【放映场次核对表.xls】文件",
392
+ accept_multiple_files=False,
393
+ type=["xls"]
394
+ )
395
 
396
  if uploaded_file:
397
  with st.spinner("文件正在处理中,请稍候..."):
398
+ # --- Route to the correct processor based on filename ---
399
+
400
+ # 1. Logic for "LED 屏幕时间表打印"
401
+ if "放映时间核对表" in uploaded_file.name:
402
+ st.subheader("LED 屏幕时间表")
403
+ schedule, date_str = process_schedule_led(uploaded_file)
404
+ if schedule is not None:
405
+ output = create_print_layout_led(schedule, date_str)
406
+ if output:
407
+ tab1, tab2 = st.tabs(["PDF 预览", "PNG 预览"])
408
+ with tab1:
409
+ st.markdown(display_pdf(output['pdf']), unsafe_allow_html=True)
410
+ with tab2:
411
+ st.image(output['png'], use_container_width=True)
412
+ else:
413
+ st.info("没有可显示的数据。")
414
+ else:
415
+ st.error("无法处理文件,请检查文件格式或内容是否正确。")
416
+
417
+ # 2. Logic for "散厅时间快捷打印"
418
+ elif "放映场次核对表" in uploaded_file.name:
419
+ part1_data, part2_data, date_str = process_schedule_quick(uploaded_file)
420
+
421
+ if part1_data is not None and part2_data is not None:
422
+ part1_output = create_print_layout_quick(part1_data, "A", date_str)
423
+ part2_output = create_print_layout_quick(part2_data, "C", date_str)
424
+
425
+ col1, col2 = st.columns(2)
426
+
427
+ with col1:
428
+ st.subheader("白班散场预览(时间 ≤ 17:30)")
429
+ if part1_output:
430
+ tab1_1, tab1_2 = st.tabs(["PDF 预览 ", "PNG 预览 "]) # Added space to make keys unique
431
+ with tab1_1:
432
+ st.markdown(display_pdf(part1_output['pdf']), unsafe_allow_html=True)
433
+ with tab1_2:
434
+ st.image(part1_output['png'])
435
+ else:
436
+ st.info("白班部分没有数据")
437
+
438
+ with col2:
439
+ st.subheader("夜班散场预览(时间 > 17:30)")
440
+ if part2_output:
441
+ tab2_1, tab2_2 = st.tabs(["PDF 预览 ", "PNG 预览 "]) # Added spaces to make keys unique
442
+ with tab2_1:
443
+ st.markdown(display_pdf(part2_output['pdf']), unsafe_allow_html=True)
444
+ with tab2_2:
445
+ st.image(part2_output['png'])
446
+ else:
447
+ st.info("夜班部分没有数据")
448
+ else:
449
+ st.error("无法处理文件,请检查文件格式或内容是否正确。")
450
+
451
+ # 3. Fallback for incorrect file
452
  else:
453
+ st.warning("文件名不匹配。请上传名为【放映时间核对表.xls】或【放映场次核对表.xls】的文件。")