Ethscriptions commited on
Commit
41c541d
·
verified ·
1 Parent(s): ee0147b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +162 -135
app.py CHANGED
@@ -6,38 +6,39 @@ import io
6
  import base64
7
  import os
8
  from datetime import datetime, timedelta
 
 
9
  from pypinyin import lazy_pinyin, Style
10
  from matplotlib.backends.backend_pdf import PdfPages
11
 
12
  def get_font(size=14):
13
- """
14
- Retrieves the specified font properties. Looks for 'simHei.ttc' first,
15
- then falls back to 'SimHei.ttf'.
16
- """
17
  font_path = "simHei.ttc"
18
  if not os.path.exists(font_path):
19
- font_path = "SimHei.ttf"
 
 
 
20
  return font_manager.FontProperties(fname=font_path, size=size)
21
 
22
  def get_pinyin_abbr(text):
23
- """
24
- Gets the first letter of the Pinyin for the first two Chinese characters of a text.
25
- """
26
- if not text:
27
  return ""
28
  # Extract the first two Chinese characters
29
- chars = [c for c in text if '\u4e00' <= c <= '\u9fff']
30
- chars = chars[:2]
31
- # Get the first letter of the Pinyin for each character
32
  pinyin_list = lazy_pinyin(chars, style=Style.FIRST_LETTER)
33
  return ''.join(pinyin_list).upper()
34
 
35
  def process_schedule(file):
36
  """
37
- Processes the uploaded Excel file to extract and clean the movie schedule data.
 
38
  """
39
  try:
40
- # Try to read the date from the Excel file
41
  date_df = pd.read_excel(file, header=None, skiprows=7, nrows=1, usecols=[3])
42
  date_str = pd.to_datetime(date_df.iloc[0, 0]).strftime('%Y-%m-%d')
43
  base_date = pd.to_datetime(date_str).date()
@@ -45,17 +46,16 @@ def process_schedule(file):
45
  # Fallback to today's date if reading fails
46
  date_str = datetime.today().strftime('%Y-%m-%d')
47
  base_date = datetime.today().date()
48
- file.seek(0) # Reset file pointer after failed read attempt
49
-
50
  try:
51
  df = pd.read_excel(file, header=9, usecols=[1, 2, 4, 5])
52
  df.columns = ['Hall', 'StartTime', 'EndTime', 'Movie']
53
 
 
54
  df['Hall'] = df['Hall'].ffill()
55
  df.dropna(subset=['StartTime', 'EndTime', 'Movie'], inplace=True)
56
-
57
- df['Hall_Num'] = df['Hall'].astype(str).str.extract(r'(\d+)').astype(int)
58
- df['Hall'] = df['Hall_Num'].astype(str) + '号'
59
 
60
  # Convert times to datetime objects
61
  df['StartTime_dt'] = pd.to_datetime(df['StartTime'], format='%H:%M', errors='coerce').apply(
@@ -65,12 +65,15 @@ def process_schedule(file):
65
  lambda t: t.replace(year=base_date.year, month=base_date.month, day=base_date.day) if pd.notnull(t) else t
66
  )
67
 
 
 
 
68
  df.loc[df['EndTime_dt'] < df['StartTime_dt'], 'EndTime_dt'] += timedelta(days=1)
69
- df = df.sort_values(['Hall_Num', 'StartTime_dt'])
70
 
71
  # Merge consecutive screenings of the same movie
72
  merged_rows = []
73
- for _, group in df.groupby('Hall_Num'):
74
  group = group.sort_values('StartTime_dt')
75
  current = None
76
  for _, row in group.iterrows():
@@ -78,7 +81,7 @@ def process_schedule(file):
78
  current = row.copy()
79
  else:
80
  if row['Movie'] == current['Movie']:
81
- current['EndTime_dt'] = row['EndTime_dt']
82
  else:
83
  merged_rows.append(current)
84
  current = row.copy()
@@ -86,137 +89,161 @@ def process_schedule(file):
86
  merged_rows.append(current)
87
 
88
  if not merged_rows:
89
- return None, date_str
90
 
91
- merged_df = pd.DataFrame(merged_rows)
92
 
93
- # Adjust start and end times
94
- merged_df['StartTime_dt'] = merged_df['StartTime_dt'] - timedelta(minutes=10)
95
- merged_df['EndTime_dt'] = merged_df['EndTime_dt'] - timedelta(minutes=5)
96
 
97
- # Create final data columns for the table
98
- merged_df['Sequence'] = merged_df.groupby('Hall_Num').cumcount() + 1
 
 
99
  merged_df['Pinyin'] = merged_df['Movie'].apply(get_pinyin_abbr)
100
- merged_df['Time'] = merged_df['StartTime_dt'].dt.strftime('%H:%M') + ' - ' + merged_df['EndTime_dt'].dt.strftime('%H:%M')
101
-
102
- final_df = merged_df[['Hall', 'Hall_Num', 'Sequence', 'Movie', 'Pinyin', 'Time']].copy()
103
- final_df = final_df.sort_values(['Hall_Num', 'Sequence']).reset_index(drop=True)
104
 
 
 
 
105
  return final_df, date_str
 
106
  except Exception as e:
107
- st.error(f"An error occurred during data processing: {e}")
108
  return None, date_str
109
 
 
110
  def create_print_layout(data, date_str):
111
  """
112
- Generates the print layout as PNG and PDF based on the processed data.
113
  """
114
  if data is None or data.empty:
115
  return None
116
 
117
- def draw_table_on_ax(fig, ax):
118
- """Helper function to draw the table on a given Matplotlib axis."""
119
- ax.set_axis_off()
120
-
121
- num_rows = len(data)
122
- if num_rows == 0:
123
- return
124
-
125
- # --- Layout & Sizing Calculations ---
126
- # Define printable area using relative coordinates (fractions of the page)
127
- TOP_MARGIN, BOTTOM_MARGIN = 0.96, 0.04
128
- LEFT_MARGIN, RIGHT_MARGIN = 0.04, 0.96
129
- TABLE_HEIGHT = TOP_MARGIN - BOTTOM_MARGIN
130
- TABLE_WIDTH = RIGHT_MARGIN - LEFT_MARGIN
131
-
132
- # Total rows for calculation: content rows + 2 for top/bottom buffer
133
- total_layout_rows = num_rows + 2
134
- row_height = TABLE_HEIGHT / total_layout_rows
135
-
136
- # Font size is 90% of the calculated row height
137
- # (row_height is a fraction of figure height, convert to points)
138
- font_size_pt = (row_height * fig.get_figheight() * 0.9) * 72
139
- font = get_font(size=font_size_pt)
140
-
141
- # Relative column widths
142
- col_relative_widths = {'hall': 1.2, 'seq': 0.8, 'movie': 5.0, 'pinyin': 1.5, 'time': 2.5}
143
- total_rel_width = sum(col_relative_widths.values())
144
-
145
- col_widths = {k: (v / total_rel_width) * TABLE_WIDTH for k, v in col_relative_widths.items()}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
 
147
- # Calculate the absolute x-position for the left edge of each column
148
- x_pos = {}
149
- current_x = LEFT_MARGIN
150
- col_order = ['hall', 'seq', 'movie', 'pinyin', 'time']
151
- for col_name in col_order:
152
- x_pos[col_name] = current_x
153
- current_x += col_widths[col_name]
154
- x_pos['end'] = current_x # The right edge of the last column
155
-
156
- # Add date header
157
- ax.text(LEFT_MARGIN, TOP_MARGIN, date_str, fontsize=12, color='#A9A9A9',
158
- ha='left', va='top', fontproperties=get_font(12), transform=ax.transAxes)
159
-
160
- # --- Drawing Loop ---
161
  for i, row in data.iterrows():
162
- # Calculate y-position for the center of the current row
163
- y_center = TOP_MARGIN - (i + 1.5) * row_height # +1.5 to account for header space
164
- y_top = y_center + row_height / 2
165
- y_bottom = y_center - row_height / 2
166
-
167
- # --- 1. Draw Cell Content ---
168
- # Column content is ordered as per requirement
169
- ax.text(x_pos['hall'] + col_widths['hall']/2, y_center, f"{row['Hall']}", va='center', ha='center', fontproperties=font, transform=ax.transAxes)
170
- ax.text(x_pos['seq'] + col_widths['seq']/2, y_center, f"{row['Sequence']}", va='center', ha='center', fontproperties=font, transform=ax.transAxes)
171
- ax.text(x_pos['movie'] + 0.01, y_center, f"{row['Movie']}", va='center', ha='left', fontproperties=font, transform=ax.transAxes, clip_on=True)
172
- ax.text(x_pos['pinyin'] + col_widths['pinyin']/2, y_center, f"{row['Pinyin']}", va='center', ha='center', fontproperties=font, transform=ax.transAxes)
173
- ax.text(x_pos['time'] + col_widths['time']/2, y_center, f"{row['Time']}", va='center', ha='center', fontproperties=font, transform=ax.transAxes)
174
-
175
- # --- 2. Draw Cell Borders ---
176
- # Draw all vertical cell lines with gray dots
177
- for col_name in col_order:
178
- ax.plot([x_pos[col_name], x_pos[col_name]], [y_bottom, y_top], color='gray', linestyle=':', linewidth=0.7)
179
- ax.plot([x_pos['end'], x_pos['end']], [y_bottom, y_top], color='gray', linestyle=':', linewidth=0.7)
180
-
181
- # Draw top border of the cell
182
- ax.plot([LEFT_MARGIN, x_pos['end']], [y_top, y_top], color='gray', linestyle=':', linewidth=0.7)
183
-
184
- # --- 3. Draw Hall Separator or Bottom Border ---
185
- # Check if this is the last movie for the current hall
186
- is_last_in_hall = (i == num_rows - 1) or (row['Hall_Num'] != data.iloc[i + 1]['Hall_Num'])
187
-
188
- if is_last_in_hall:
189
- # If it's the last entry for a hall, draw a solid black line below it
190
- ax.plot([LEFT_MARGIN, x_pos['end']], [y_bottom, y_bottom], color='black', linestyle='-', linewidth=1.2, zorder=3)
191
- else:
192
- # Otherwise, draw a standard gray dotted line
193
- ax.plot([LEFT_MARGIN, x_pos['end']], [y_bottom, y_bottom], color='gray', linestyle=':', linewidth=0.7)
194
-
195
- # --- Generate PNG and PDF Outputs ---
196
- # Create a figure for PNG output
197
- png_fig = plt.figure(figsize=(8.27, 11.69), dpi=300)
198
- png_ax = png_fig.add_subplot(111)
199
- draw_table_on_ax(png_fig, png_ax)
200
-
201
- # Create a separate figure for PDF output
202
- pdf_fig = plt.figure(figsize=(8.27, 11.69), dpi=300)
203
- pdf_ax = pdf_fig.add_subplot(111)
204
- draw_table_on_ax(pdf_fig, pdf_ax)
205
 
206
- # Save PNG to a buffer
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  png_buffer = io.BytesIO()
208
- png_fig.savefig(png_buffer, format='png', bbox_inches='tight', pad_inches=0.05)
209
  png_buffer.seek(0)
210
  image_base64 = base64.b64encode(png_buffer.getvalue()).decode()
211
  plt.close(png_fig)
212
-
213
- # Save PDF to a buffer
214
  pdf_buffer = io.BytesIO()
215
- pdf_fig.savefig(pdf_buffer, format='pdf', bbox_inches='tight', pad_inches=0.05)
216
  pdf_buffer.seek(0)
217
  pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode()
218
  plt.close(pdf_fig)
219
-
220
  return {
221
  'png': f"data:image/png;base64,{image_base64}",
222
  'pdf': f"data:application/pdf;base64,{pdf_base64}"
@@ -227,18 +254,17 @@ def display_pdf(base64_pdf):
227
  pdf_display = f'<iframe src="{base64_pdf}" width="100%" height="800" type="application/pdf"></iframe>'
228
  return pdf_display
229
 
230
- # --- Streamlit App UI ---
231
  st.set_page_config(page_title="LED 屏幕时间表打印", layout="wide")
232
  st.title("LED 屏幕时间表打印")
233
 
234
- uploaded_file = st.file_uploader("选择打开【放映时间核对表.xls】文件", accept_multiple_files=False, type=["xls", "xlsx"])
235
 
236
  if uploaded_file:
237
  with st.spinner("文件正在处理中,请稍候..."):
238
- schedule, date_str = process_schedule(io.BytesIO(uploaded_file.getvalue()))
239
  if schedule is not None and not schedule.empty:
240
  output = create_print_layout(schedule, date_str)
241
-
242
  if output:
243
  # Create tabs to switch between PDF and PNG previews
244
  tab1, tab2 = st.tabs(["PDF 预览", "PNG 预览"])
@@ -249,7 +275,8 @@ if uploaded_file:
249
  with tab2:
250
  st.image(output['png'], use_container_width=True)
251
  else:
252
- st.error("生成打印布局时出错。")
253
- else:
254
- st.error("无法处理文件,或文件中没有找到有效排片数据。请检查文件格式或内容是否正确。")
255
-
 
 
6
  import base64
7
  import os
8
  from datetime import datetime, timedelta
9
+ import numpy as np
10
+ from matplotlib.backends.backend_agg import FigureCanvasAgg
11
  from pypinyin import lazy_pinyin, Style
12
  from matplotlib.backends.backend_pdf import PdfPages
13
 
14
  def get_font(size=14):
15
+ """Loads the specified font, with a fallback."""
 
 
 
16
  font_path = "simHei.ttc"
17
  if not os.path.exists(font_path):
18
+ font_path = "SimHei.ttf" # Fallback font
19
+ if not os.path.exists(font_path):
20
+ st.warning("Font file (simHei.ttc or SimHei.ttf) not found. Display may be incorrect.")
21
+ return font_manager.FontProperties(size=size)
22
  return font_manager.FontProperties(fname=font_path, size=size)
23
 
24
  def get_pinyin_abbr(text):
25
+ """Gets the first letter of the Pinyin for the first two Chinese characters of a text."""
26
+ if not isinstance(text, str):
 
 
27
  return ""
28
  # Extract the first two Chinese characters
29
+ chars = [c for c in text if '\u4e00' <= c <= '\u9fff'][:2]
30
+ if not chars:
31
+ return ""
32
  pinyin_list = lazy_pinyin(chars, style=Style.FIRST_LETTER)
33
  return ''.join(pinyin_list).upper()
34
 
35
  def process_schedule(file):
36
  """
37
+ Processes the uploaded Excel file to extract and clean the movie schedule.
38
+ This version also prepares all data fields needed for the new layout.
39
  """
40
  try:
41
+ # Try to read the date from the specified cell
42
  date_df = pd.read_excel(file, header=None, skiprows=7, nrows=1, usecols=[3])
43
  date_str = pd.to_datetime(date_df.iloc[0, 0]).strftime('%Y-%m-%d')
44
  base_date = pd.to_datetime(date_str).date()
 
46
  # Fallback to today's date if reading fails
47
  date_str = datetime.today().strftime('%Y-%m-%d')
48
  base_date = datetime.today().date()
49
+
 
50
  try:
51
  df = pd.read_excel(file, header=9, usecols=[1, 2, 4, 5])
52
  df.columns = ['Hall', 'StartTime', 'EndTime', 'Movie']
53
 
54
+ # Data Cleaning
55
  df['Hall'] = df['Hall'].ffill()
56
  df.dropna(subset=['StartTime', 'EndTime', 'Movie'], inplace=True)
57
+ df['Hall'] = df['Hall'].astype(str).str.extract(r'(\d+号)')
58
+ df.dropna(subset=['Hall'], inplace=True) # Ensure rows without a hall number are dropped
 
59
 
60
  # Convert times to datetime objects
61
  df['StartTime_dt'] = pd.to_datetime(df['StartTime'], format='%H:%M', errors='coerce').apply(
 
65
  lambda t: t.replace(year=base_date.year, month=base_date.month, day=base_date.day) if pd.notnull(t) else t
66
  )
67
 
68
+ df.dropna(subset=['StartTime_dt', 'EndTime_dt'], inplace=True)
69
+
70
+ # Handle screenings that cross midnight
71
  df.loc[df['EndTime_dt'] < df['StartTime_dt'], 'EndTime_dt'] += timedelta(days=1)
72
+ df = df.sort_values(['Hall', 'StartTime_dt'])
73
 
74
  # Merge consecutive screenings of the same movie
75
  merged_rows = []
76
+ for _, group in df.groupby('Hall'):
77
  group = group.sort_values('StartTime_dt')
78
  current = None
79
  for _, row in group.iterrows():
 
81
  current = row.copy()
82
  else:
83
  if row['Movie'] == current['Movie']:
84
+ current['EndTime_dt'] = row['EndTime_dt'] # Extend the end time
85
  else:
86
  merged_rows.append(current)
87
  current = row.copy()
 
89
  merged_rows.append(current)
90
 
91
  if not merged_rows:
92
+ return None, date_str
93
 
94
+ merged_df = pd.DataFrame(merged_rows).reset_index(drop=True)
95
 
96
+ # Adjust times as per original logic
97
+ merged_df['StartTime_dt'] -= timedelta(minutes=10)
98
+ merged_df['EndTime_dt'] -= timedelta(minutes=5)
99
 
100
+ # --- New Data Preparation for Layout ---
101
+ # 1. Create Index (序号)
102
+ merged_df['Index'] = merged_df.groupby('Hall').cumcount() + 1
103
+ # 2. Create Pinyin Abbreviation (拼音缩写)
104
  merged_df['Pinyin'] = merged_df['Movie'].apply(get_pinyin_abbr)
105
+ # 3. Create Time String (时间)
106
+ merged_df['TimeStr'] = merged_df['StartTime_dt'].dt.strftime('%H:%M') + ' - ' + merged_df['EndTime_dt'].dt.strftime('%H:%M')
107
+ # 4. Clean Hall Number for display
108
+ merged_df['Hall'] = merged_df['Hall'].str.replace('', '')
109
 
110
+ # Select and reorder columns as per requirement
111
+ final_df = merged_df[['Hall', 'Index', 'Movie', 'Pinyin', 'TimeStr']]
112
+
113
  return final_df, date_str
114
+
115
  except Exception as e:
116
+ st.error(f"An error occurred during file processing: {e}")
117
  return None, date_str
118
 
119
+
120
  def create_print_layout(data, date_str):
121
  """
122
+ Creates the print layout on an A4 page based on a dynamic grid system.
123
  """
124
  if data is None or data.empty:
125
  return None
126
 
127
+ # --- 1. Layout Constants ---
128
+ A4_WIDTH_IN, A4_HEIGHT_IN = 8.27, 11.69
129
+ MARGIN_IN = 0.4
130
+ USABLE_WIDTH_IN = A4_WIDTH_IN - (2 * MARGIN_IN)
131
+ USABLE_HEIGHT_IN = A4_HEIGHT_IN - (2 * MARGIN_IN)
132
+
133
+ # --- 2. Row and Font Calculation ---
134
+ num_content_rows = len(data)
135
+ total_grid_rows = num_content_rows + 2 # Add 2 for top/bottom padding rows
136
+ row_height_in = USABLE_HEIGHT_IN / total_grid_rows
137
+ # Calculate font size in points (1 inch = 72 points) to be 90% of row height
138
+ font_size_pt = (row_height_in * 72) * 0.9
139
+ content_font = get_font(font_size_pt)
140
+ date_font = get_font(12)
141
+
142
+ # --- 3. Column Width Calculation ---
143
+ # Create a temporary figure to calculate text widths accurately
144
+ temp_fig = plt.figure(figsize=(A4_WIDTH_IN, A4_HEIGHT_IN))
145
+ canvas = FigureCanvasAgg(temp_fig)
146
+
147
+ cols_to_measure = ['Hall', 'Index', 'Movie', 'Pinyin', 'TimeStr']
148
+ col_widths_in = []
149
+
150
+ for col in cols_to_measure:
151
+ # Find the longest string in the column for measurement
152
+ longest_item = max(data[col].astype(str).tolist(), key=len, default="")
153
+ # Create a temporary text object to measure its width
154
+ t = plt.text(0, 0, longest_item, fontproperties=content_font)
155
+ # Get the bounding box of the text in display units and convert to inches
156
+ bbox = t.get_window_extent(renderer=canvas.get_renderer())
157
+ width_in = bbox.width / temp_fig.dpi
158
+ col_widths_in.append(width_in * 1.1) # Add 10% padding
159
+ t.remove()
160
+
161
+ plt.close(temp_fig) # Close the temporary figure
162
+
163
+ # Scale column widths to fit the usable page width
164
+ total_calculated_width = sum(col_widths_in)
165
+ scale_factor = USABLE_WIDTH_IN / total_calculated_width if total_calculated_width > 0 else 1
166
+ final_col_widths_in = [w * scale_factor for w in col_widths_in]
167
+
168
+ # --- 4. Figure and PDF/PNG Generation ---
169
+ def process_figure(fig, ax):
170
+ # Calculate grid coordinates in Axes units (0 to 1)
171
+ col_widths_ax = [w / USABLE_WIDTH_IN for w in final_col_widths_in]
172
+ row_height_ax = 1.0 / total_grid_rows
173
 
174
+ x_coords_ax = [0] + np.cumsum(col_widths_ax).tolist()
175
+ y_coords_ax = [1 - i * row_height_ax for i in range(total_grid_rows + 1)]
176
+
177
+ # Add date string at the top-left of the usable area
178
+ ax.text(0, 1, date_str, transform=ax.transAxes, fontproperties=date_font,
179
+ ha='left', va='bottom', color='#A9A9A9')
180
+
181
+ # --- Draw Grid and Content ---
 
 
 
 
 
 
182
  for i, row in data.iterrows():
183
+ grid_row_index = i + 1 # Offset by 1 for the top padding row
184
+ y_bottom = y_coords_ax[grid_row_index + 1]
185
+ y_center = y_bottom + row_height_ax / 2
186
+
187
+ # Draw bottom dotted line for the current row's cells
188
+ ax.plot([0, 1], [y_bottom, y_bottom], transform=ax.transAxes,
189
+ linestyle=':', color='gray', linewidth=0.7)
190
+
191
+ # Draw content for each cell in the row
192
+ content_list = [row['Hall'], row['Index'], row['Movie'], row['Pinyin'], row['TimeStr']]
193
+ for j, content in enumerate(content_list):
194
+ x_left = x_coords_ax[j]
195
+ x_center = x_left + col_widths_ax[j] / 2
196
+ ax.text(x_center, y_center, content, transform=ax.transAxes,
197
+ fontproperties=content_font, ha='center', va='center')
198
+
199
+ # --- Draw Vertical Grid Lines ---
200
+ content_area_top_y = y_coords_ax[1]
201
+ content_area_bottom_y = y_coords_ax[-2]
202
+ for x in x_coords_ax[1:-1]:
203
+ ax.plot([x, x], [content_area_bottom_y, content_area_top_y], transform=ax.transAxes,
204
+ linestyle=':', color='gray', linewidth=0.7)
205
+
206
+ # --- Draw Black Separator Lines Between Halls ---
207
+ hall_change_indices = data.index[data['Hall'] != data['Hall'].shift(-1)]
208
+ for idx in hall_change_indices:
209
+ # The line is at the bottom of the current row
210
+ y_line = y_coords_ax[idx + 2] # +1 for top margin, +1 to get bottom of current row
211
+ ax.plot([0, 1], [y_line, y_line], transform=ax.transAxes,
212
+ linestyle='-', color='black', linewidth=1.2)
213
+
214
+
215
+ # Create figures for PNG and PDF
216
+ png_fig = plt.figure(figsize=(A4_WIDTH_IN, A4_HEIGHT_IN), dpi=300)
217
+ pdf_fig = plt.figure(figsize=(A4_WIDTH_IN, A4_HEIGHT_IN), dpi=300)
 
 
 
 
 
 
 
 
218
 
219
+ # Configure axes to fill the usable area defined by margins
220
+ ax_rect = [
221
+ MARGIN_IN / A4_WIDTH_IN, MARGIN_IN / A4_HEIGHT_IN,
222
+ USABLE_WIDTH_IN / A4_WIDTH_IN, USABLE_HEIGHT_IN / A4_HEIGHT_IN
223
+ ]
224
+ png_ax = png_fig.add_axes(ax_rect)
225
+ pdf_ax = pdf_fig.add_axes(ax_rect)
226
+ png_ax.axis('off')
227
+ pdf_ax.axis('off')
228
+
229
+ # Process both figures
230
+ process_figure(png_fig, png_ax)
231
+ process_figure(pdf_fig, pdf_ax)
232
+
233
+ # Save PNG to buffer
234
  png_buffer = io.BytesIO()
235
+ png_fig.savefig(png_buffer, format='png', pad_inches=0)
236
  png_buffer.seek(0)
237
  image_base64 = base64.b64encode(png_buffer.getvalue()).decode()
238
  plt.close(png_fig)
239
+
240
+ # Save PDF to buffer
241
  pdf_buffer = io.BytesIO()
242
+ pdf_fig.savefig(pdf_buffer, format='pdf', pad_inches=0)
243
  pdf_buffer.seek(0)
244
  pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode()
245
  plt.close(pdf_fig)
246
+
247
  return {
248
  'png': f"data:image/png;base64,{image_base64}",
249
  'pdf': f"data:application/pdf;base64,{pdf_base64}"
 
254
  pdf_display = f'<iframe src="{base64_pdf}" width="100%" height="800" type="application/pdf"></iframe>'
255
  return pdf_display
256
 
257
+ # --- Streamlit App Main Body ---
258
  st.set_page_config(page_title="LED 屏幕时间表打印", layout="wide")
259
  st.title("LED 屏幕时间表打印")
260
 
261
+ uploaded_file = st.file_uploader("选择打开【放映时间核对表.xls】文件", type=["xls"])
262
 
263
  if uploaded_file:
264
  with st.spinner("文件正在处理中,请稍候..."):
265
+ schedule, date_str = process_schedule(uploaded_file)
266
  if schedule is not None and not schedule.empty:
267
  output = create_print_layout(schedule, date_str)
 
268
  if output:
269
  # Create tabs to switch between PDF and PNG previews
270
  tab1, tab2 = st.tabs(["PDF 预览", "PNG 预览"])
 
275
  with tab2:
276
  st.image(output['png'], use_container_width=True)
277
  else:
278
+ st.error("生成打印布局失败。")
279
+ elif schedule is None:
280
+ st.error("无法处理文件,请检查文件格式或内容是否正确。")
281
+ else: # schedule is empty
282
+ st.warning("处理完成,但文件中没有找到有效的排片数据。")