Ethscriptions commited on
Commit
b7d31cf
·
verified ·
1 Parent(s): 9a3e51f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +278 -156
app.py CHANGED
@@ -1,137 +1,283 @@
1
  import pandas as pd
2
  import streamlit as st
3
- from datetime import datetime, timedelta
4
  import matplotlib.pyplot as plt
 
5
  import io
6
  import base64
7
- import matplotlib.gridspec as gridspec
 
8
  import math
 
9
  from matplotlib.backends.backend_pdf import PdfPages
 
10
  from matplotlib.patches import FancyBboxPatch
11
 
12
- # --- Constants ---
13
  SPLIT_TIME = "17:30"
14
  BUSINESS_START = "09:30"
15
  BUSINESS_END = "01:30"
16
  BORDER_COLOR = '#A9A9A9'
17
  DATE_COLOR = '#A9A9A9'
18
 
19
- def process_schedule(file):
20
- """处理上传的 Excel 文件,生成排序和分组后的打印内容"""
 
 
 
 
 
21
  try:
22
- # 读取 Excel,跳过前 8 行
23
- df = pd.read_excel(file, skiprows=8)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- # 提取所需列 (G, H, J)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  df = df.iloc[:, [6, 7, 9]]
27
  df.columns = ['Hall', 'StartTime', 'EndTime']
28
-
29
- # 清理数据
30
  df = df.dropna(subset=['Hall', 'StartTime', 'EndTime'])
31
-
32
- # 转换影厅格式为 "#号" 格式
33
  df['Hall'] = df['Hall'].str.extract(r'(\d+)号').astype(str) + ' '
34
 
35
- # 保存原始时间字符串用于诊断
36
- df['original_end'] = df['EndTime']
37
-
38
- # 转换时间为 datetime 对象
39
  base_date = datetime.today().date()
40
  df['StartTime'] = pd.to_datetime(df['StartTime'])
41
  df['EndTime'] = pd.to_datetime(df['EndTime'])
42
-
43
- # 设置基准时间
44
  business_start = datetime.strptime(f"{base_date} {BUSINESS_START}", "%Y-%m-%d %H:%M")
45
  business_end = datetime.strptime(f"{base_date} {BUSINESS_END}", "%Y-%m-%d %H:%M")
46
-
47
- # 处理跨天情况
48
  if business_end < business_start:
49
  business_end += timedelta(days=1)
50
-
51
- # 标准化所有时间到同一天或第二天
52
  for idx, row in df.iterrows():
53
  end_time = row['EndTime']
54
- # If end time is early in the morning (e.g., after midnight), move it to the next day
55
  if end_time.hour < 9:
56
  df.at[idx, 'EndTime'] = end_time + timedelta(days=1)
57
- # Handle cases starting late and ending after midnight
58
- elif row['StartTime'].hour >= 21 and end_time.hour < 9:
59
  df.at[idx, 'EndTime'] = end_time + timedelta(days=1)
60
 
61
- # 创建用于比较的时间列,不受日期影响
62
- df['time_for_comparison'] = df['EndTime'].apply(
63
- lambda x: datetime.combine(base_date, x.time())
64
- )
65
- # 确保凌晨的时间被推到第二天进行比较
66
  df.loc[df['time_for_comparison'].dt.hour < 9, 'time_for_comparison'] += timedelta(days=1)
67
 
68
- # 筛选营业时间内的场次
69
  valid_times = (
70
  (df['time_for_comparison'] >= datetime.combine(base_date, business_start.time())) &
71
- (df['time_for_comparison'] <= business_end)
72
  )
73
-
74
  df = df[valid_times]
75
-
76
- # 按散场时间排序
77
  df = df.sort_values('EndTime')
78
 
79
- # 分割数据
80
  split_time_dt = datetime.strptime(f"{base_date} {SPLIT_TIME}", "%Y-%m-%d %H:%M")
81
 
82
- part1 = df[df['EndTime'] <= split_time_dt].copy()
83
- part2 = df[df['EndTime'] > split_time_dt].copy()
84
-
85
- # 格式化时间显示
86
  for part in [part1, part2]:
87
  part['EndTime'] = part['EndTime'].dt.strftime('%-H:%M')
88
 
89
- # 精确读取C6单元格以获取日期
90
- date_df = pd.read_excel(
91
- file,
92
- skiprows=5, # 跳过前5行(0-4)
93
- nrows=1, # 只读1行
94
- usecols=[2], # 第三列(C列)
95
- header=None # 无表头
96
- )
97
  date_cell = date_df.iloc[0, 0]
98
 
99
  try:
100
- # 处理不同日期格式
101
  if isinstance(date_cell, str):
102
  date_str = datetime.strptime(date_cell, '%Y-%m-%d').strftime('%Y-%m-%d')
103
  else:
104
  date_str = pd.to_datetime(date_cell).strftime('%Y-%m-%d')
105
  except:
106
  date_str = datetime.today().strftime('%Y-%m-%d')
107
-
108
  return part1[['Hall', 'EndTime']], part2[['Hall', 'EndTime']], date_str
109
-
110
  except Exception as e:
111
  st.error(f"处理文件时出错: {str(e)}")
112
  return None, None, None
113
 
114
- def create_print_layout(data, title, date_str):
115
- """创建打印布局 (PNG PDF),动态调整字体大小"""
 
116
  if data.empty:
117
  return None
118
 
119
- # --- 创建 PNG 图形 ---
120
- png_fig = plt.figure(figsize=(5.83, 8.27), dpi=300) # A5 竖向
121
- png_ax_container = png_fig.add_subplot(111)
122
- png_ax_container.set_axis_off()
123
  png_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
124
 
125
- # --- 创建 PDF 图形 ---
126
- pdf_fig = plt.figure(figsize=(5.83, 8.27), dpi=300) # A5 竖向
127
- pdf_ax_container = pdf_fig.add_subplot(111)
128
- pdf_ax_container.set_axis_off()
129
  pdf_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
130
 
131
- # --- 内部绘图函数 ---
132
  def process_figure(fig, is_pdf=False):
133
  plt.rcParams['font.family'] = 'sans-serif'
134
- plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'Heiti TC', 'SimHei'] # 添加备用中文字体
135
 
136
  total_items = len(data)
137
  num_cols = 3
@@ -139,14 +285,12 @@ def create_print_layout(data, title, date_str):
139
 
140
  gs = gridspec.GridSpec(num_rows + 1, num_cols, hspace=0.05, wspace=0.05, height_ratios=[0.1] + [1] * num_rows, figure=fig)
141
 
142
- # --- 预先计算单元格的目标宽度(以像素为单位)---
143
- target_width_px = 1
144
  if total_items > 0:
145
  ax_temp = fig.add_subplot(gs[1, 0])
146
  fig.canvas.draw()
147
  target_width_px = ax_temp.get_window_extent().width * 0.90
148
  ax_temp.remove()
149
- # --- 预计算结束 ---
150
 
151
  available_height_per_row = (8.27 * 0.9 * (1 / 1.2)) / num_rows if num_rows > 0 else 1
152
  date_fontsize = min(40, max(10, available_height_per_row * 72 * 0.5))
@@ -165,7 +309,6 @@ def create_print_layout(data, title, date_str):
165
  if new_index < len(sorted_data):
166
  sorted_data[new_index] = item
167
 
168
- # 绘制数据单元格
169
  for idx, (hall, end_time) in enumerate(sorted_data):
170
  if hall and end_time:
171
  row_grid = idx // num_cols + 1
@@ -185,8 +328,6 @@ def create_print_layout(data, title, date_str):
185
  ax.add_patch(bbox)
186
 
187
  display_text = f"{hall}{end_time}"
188
-
189
- # --- 动态字体大小调整逻辑 ---
190
  t = ax.text(0.5, 0.5, display_text,
191
  fontweight='bold', ha='center', va='center',
192
  transform=ax.transAxes)
@@ -195,16 +336,13 @@ def create_print_layout(data, title, date_str):
195
  while current_size > 1:
196
  t.set_fontsize(current_size)
197
  text_bbox = t.get_window_extent(renderer=fig.canvas.get_renderer())
198
-
199
  if text_bbox.width <= target_width_px:
200
  break
201
  current_size -= 2
202
- # --- 修改结束 ---
203
 
204
  ax.set_xticks([])
205
  ax.set_yticks([])
206
 
207
- # 添加日期信息
208
  ax_date = fig.add_subplot(gs[0, :])
209
  ax_date.text(0.01, 0.5, f"{date_str} {title}",
210
  fontsize=date_fontsize * 0.5,
@@ -216,18 +354,15 @@ def create_print_layout(data, title, date_str):
216
  ax_date.set_yticks([])
217
  ax_date.set_facecolor('none')
218
 
219
- # --- 处理图形 ---
220
  process_figure(png_fig)
221
  process_figure(pdf_fig, is_pdf=True)
222
 
223
- # --- 保存 PNG ---
224
  png_buffer = io.BytesIO()
225
  png_fig.savefig(png_buffer, format='png', bbox_inches='tight', pad_inches=0.02)
226
  png_buffer.seek(0)
227
  png_base64 = base64.b64encode(png_buffer.getvalue()).decode()
228
  plt.close(png_fig)
229
 
230
- # --- 保存 PDF ---
231
  pdf_buffer = io.BytesIO()
232
  with PdfPages(pdf_buffer) as pdf:
233
  pdf.savefig(pdf_fig, bbox_inches='tight', pad_inches=0.02)
@@ -240,92 +375,79 @@ def create_print_layout(data, title, date_str):
240
  'pdf': f'data:application/pdf;base64,{pdf_base64}'
241
  }
242
 
 
243
  def display_pdf(base64_pdf):
244
- """Streamlit中嵌入显示PDF"""
245
- pdf_display = f'<iframe src="{base64_pdf}" width="100%" height="800" type="application/pdf"></iframe>'
 
 
246
  return pdf_display
247
 
248
- # --- Streamlit 界面 ---
249
- st.set_page_config(page_title="散厅时间快捷打印", layout="wide")
250
- st.title("散厅时间快捷打印")
251
 
252
- uploaded_file = st.file_uploader("上传【放映场次核对表.xls】文件", type=["xls"])
 
 
 
 
253
 
254
  if uploaded_file:
255
- with st.spinner("文件处理中..."):
256
- part1, part2, date_str = process_schedule(uploaded_file)
257
-
258
- if part1 is not None and part2 is not None:
259
- with st.spinner("正在生成布局..."):
260
- part1_output = create_print_layout(part1, "A", date_str)
261
- part2_output = create_print_layout(part2, "C", date_str)
262
-
263
- col1, col2 = st.columns(2)
264
-
265
- with col1:
266
- st.subheader("白班散场预览(时间 ≤ 17:30)")
267
- if part1_output:
268
- # --- 新增:下载按钮 ---
269
- png_data_1 = base64.b64decode(part1_output['png'].split(',')[1])
270
- pdf_data_1 = base64.b64decode(part1_output['pdf'].split(',')[1])
271
-
272
- btn_col1, btn_col2 = st.columns(2)
273
- with btn_col1:
274
- st.download_button(
275
- label="下载 PNG 图片",
276
- data=png_data_1,
277
- file_name=f"{date_str}-白班-A.png",
278
- mime="image/png",
279
- use_container_width=True
280
- )
281
- with btn_col2:
282
- st.download_button(
283
- label="下载 PDF 文件",
284
- data=pdf_data_1,
285
- file_name=f"{date_str}-白班-A.pdf",
286
- mime="application/pdf",
287
- use_container_width=True
288
- )
289
- # --- 新增结束 ---
290
-
291
- tab1_1, tab1_2 = st.tabs(["PDF 预览", "PNG 预览"])
292
- with tab1_1:
293
- st.markdown(display_pdf(part1_output['pdf']), unsafe_allow_html=True)
294
- with tab1_2:
295
- st.image(part1_output['png'])
296
  else:
297
- st.info("白班部分没有数据")
298
-
299
- with col2:
300
- st.subheader("夜班散场预览(时间 > 17:30)")
301
- if part2_output:
302
- # --- 新增:下载按钮 ---
303
- png_data_2 = base64.b64decode(part2_output['png'].split(',')[1])
304
- pdf_data_2 = base64.b64decode(part2_output['pdf'].split(',')[1])
305
-
306
- btn_col3, btn_col4 = st.columns(2)
307
- with btn_col3:
308
- st.download_button(
309
- label="下载 PNG 图片",
310
- data=png_data_2,
311
- file_name=f"{date_str}-夜班-C.png",
312
- mime="image/png",
313
- use_container_width=True
314
- )
315
- with btn_col4:
316
- st.download_button(
317
- label="下载 PDF 文件",
318
- data=pdf_data_2,
319
- file_name=f"{date_str}-夜班-C.pdf",
320
- mime="application/pdf",
321
- use_container_width=True
322
- )
323
- # --- 新增结束 ---
324
-
325
- tab2_1, tab2_2 = st.tabs(["PDF 预览", "PNG 预览"])
326
- with tab2_1:
327
- st.markdown(display_pdf(part2_output['pdf']), unsafe_allow_html=True)
328
- with tab2_2:
329
- st.image(part2_output['png'])
330
  else:
331
- st.info("夜班部分没有数据")
 
 
 
 
 
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 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."""
39
+ if not text:
40
+ return ""
41
+ # Extract the first two Chinese characters
42
+ chars = [c for c in text if '\u4e00' <= c <= '\u9fff']
43
+ chars = chars[:2]
44
+ # Get the first letter of the pinyin for each character
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])
54
+ date_str = pd.to_datetime(date_df.iloc[0, 0]).strftime('%Y-%m-%d')
55
+ base_date = pd.to_datetime(date_str).date()
56
+ except Exception:
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
71
+ )
72
+ df['EndTime_dt'] = pd.to_datetime(df['EndTime'], format='%H:%M', errors='coerce').apply(
73
+ 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'):
81
+ group = group.sort_values('StartTime_dt')
82
+ current = None
83
+ for _, row in group.iterrows():
84
+ if current is None:
85
+ current = row.copy()
86
+ else:
87
+ if row['Movie'] == current['Movie']:
88
+ current['EndTime_dt'] = row['EndTime_dt']
89
+ else:
90
+ merged_rows.append(current)
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
+
115
+ # Create figures for PNG and PDF output with A4 dimensions
116
+ png_fig = plt.figure(figsize=(8.27, 11.69), dpi=300)
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)
196
+ pdf_buffer.seek(0)
197
+ pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode()
198
+ plt.close(pdf_fig)
199
+
200
+ return {
201
+ 'png': f"data:image/png;base64,{image_base64}",
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
 
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))
 
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
 
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)
 
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,
 
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)
 
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"""
382
+ <iframe src="{base64_pdf}" width="100%" height="800" type="application/pdf"></iframe>
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】的文件。")