Ethscriptions commited on
Commit
a1d5478
·
verified ·
1 Parent(s): 1ca624e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +261 -143
app.py CHANGED
@@ -1,178 +1,304 @@
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
 
11
- # Constants
12
  SPLIT_TIME = "17:30"
13
  BUSINESS_START = "09:30"
14
  BUSINESS_END = "01:30"
15
- BORDER_COLOR = 'grey' # Changed to grey for the new border
16
  DATE_COLOR = '#A9A9A9'
17
  A5_WIDTH_IN = 5.83
18
  A5_HEIGHT_IN = 8.27
19
  NUM_COLS = 3
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
- def process_schedule(file):
23
- """处理上传的 Excel 文件,生成排序和分组后的打印内容"""
24
  try:
25
- # 读取 Excel,跳过前 8
26
- df = pd.read_excel(file, skiprows=8)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
- # 提取所需列 (G9, H9, J9)
29
- df = df.iloc[:, [6, 7, 9]] # G, H, J 列
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  df.columns = ['Hall', 'StartTime', 'EndTime']
31
-
32
- # 清理数据
33
  df = df.dropna(subset=['Hall', 'StartTime', 'EndTime'])
34
-
35
- # 转换影厅格式为 "#号" 格式
36
  df['Hall'] = df['Hall'].str.extract(r'(\d+)号').astype(str) + ' '
37
-
38
- # 保存原始时间字符串用于诊断
39
  df['original_end'] = df['EndTime']
40
-
41
- # 转换时间为 datetime 对象
42
  base_date = datetime.today().date()
43
  df['StartTime'] = pd.to_datetime(df['StartTime'])
44
  df['EndTime'] = pd.to_datetime(df['EndTime'])
45
-
46
- # 设置基准时间
47
  business_start = datetime.strptime(f"{base_date} {BUSINESS_START}", "%Y-%m-%d %H:%M")
48
  business_end = datetime.strptime(f"{base_date} {BUSINESS_END}", "%Y-%m-%d %H:%M")
49
-
50
- # 处理跨天情况
51
  if business_end < business_start:
52
  business_end += timedelta(days=1)
53
-
54
- # 标准化所有时间到同一天
55
  for idx, row in df.iterrows():
56
  end_time = row['EndTime']
57
  if end_time.hour < 9:
58
  df.at[idx, 'EndTime'] = end_time + timedelta(days=1)
59
-
60
  if row['StartTime'].hour >= 21 and end_time.hour < 9:
61
  df.at[idx, 'EndTime'] = end_time + timedelta(days=1)
62
-
63
- # 筛选营业时间内的场次
64
  df['time_for_comparison'] = df['EndTime'].apply(
65
  lambda x: datetime.combine(base_date, x.time())
66
  )
67
-
68
  df.loc[df['time_for_comparison'].dt.hour < 9, 'time_for_comparison'] += timedelta(days=1)
69
-
70
  valid_times = (
71
- ((df['time_for_comparison'] >= datetime.combine(base_date, business_start.time())) &
72
- (df['time_for_comparison'] <= datetime.combine(base_date + timedelta(days=1), business_end.time())))
73
  )
74
-
75
  df = df[valid_times]
76
-
77
- # 按散场时间排序
78
  df = df.sort_values('EndTime')
79
-
80
- # 分割数据
81
  split_time = datetime.strptime(f"{base_date} {SPLIT_TIME}", "%Y-%m-%d %H:%M")
82
  split_time_for_comparison = df['time_for_comparison'].apply(
83
  lambda x: datetime.combine(base_date, split_time.time())
84
  )
85
-
86
  part1 = df[df['time_for_comparison'] <= split_time_for_comparison].copy()
87
  part2 = df[df['time_for_comparison'] > split_time_for_comparison].copy()
88
-
89
- # 格式化时间显示
90
  for part in [part1, part2]:
91
  part['EndTime'] = part['EndTime'].dt.strftime('%-H:%M')
92
-
93
- # 精确读取C6单元格
94
  date_df = pd.read_excel(
95
  file,
96
- skiprows=5, # 跳过前5行(0-4)
97
- nrows=1, # 只读1行
98
- usecols=[2], # 第三列(C列)
99
- header=None # 无表头
100
  )
101
  date_cell = date_df.iloc[0, 0]
102
-
103
  try:
104
- # 处理不同日期格式
105
  if isinstance(date_cell, str):
106
  date_str = datetime.strptime(date_cell, '%Y-%m-%d').strftime('%Y-%m-%d')
107
  else:
108
  date_str = pd.to_datetime(date_cell).strftime('%Y-%m-%d')
109
  except:
110
  date_str = datetime.today().strftime('%Y-%m-%d')
111
-
112
  return part1[['Hall', 'EndTime']], part2[['Hall', 'EndTime']], date_str
113
-
114
  except Exception as e:
115
  st.error(f"处理文件时出错: {str(e)}")
116
  return None, None, None
117
 
118
-
119
- def create_print_layout(data, title, date_str):
120
- """创建精确的 A5 表格打印布局 (PNG 和 PDF)"""
121
  if data.empty:
122
  return None
123
 
124
- # --- 内部绘图函数 ---
125
  def generate_figure():
126
- # --- 1. 计算布局和字体大小 ---
127
  total_items = len(data)
128
  num_rows = math.ceil(total_items / NUM_COLS) if total_items > 0 else 1
129
-
130
- # 定义日期标题行的高度(英寸),数据行将填充剩余空间
131
  date_header_height_in = 0.3
132
  data_area_height_in = A5_HEIGHT_IN - date_header_height_in
133
-
134
- # 计算每个数据单元格的尺寸(英寸)
135
  cell_width_in = A5_WIDTH_IN / NUM_COLS
136
  cell_height_in = data_area_height_in / num_rows
137
-
138
- # 将单元格宽度转换为点(1 英寸 = 72 点)
139
  cell_width_pt = cell_width_in * 72
140
  cell_height_pt = cell_height_in * 72
141
-
142
- # --- 动态字体大小计算 ---
143
- # 目标:文本总宽度为单元格宽度的 90%
144
  target_text_width_pt = cell_width_pt * 0.9
145
- # 启发式估算:假设最长文本为 "10 23:59" (8个字符),平均字符宽度约为字体大小的0.6倍
146
- # FONT_SIZE = target_width / (num_chars * avg_char_width_factor)
147
  fontsize_from_width = target_text_width_pt / (8 * 0.6)
148
- # 字体高度不能超过单元格高度(留出20%的垂直边距)
149
  fontsize_from_height = cell_height_pt * 0.8
150
- # 选择两者中较小的一个,以确保文本能完全容纳
151
  base_fontsize = min(fontsize_from_width, fontsize_from_height)
152
-
153
- # --- 2. 创建图形和网格 ---
154
  fig = plt.figure(figsize=(A5_WIDTH_IN, A5_HEIGHT_IN), dpi=300)
155
- # 设置无边距,让网格填满整个图纸
156
  fig.subplots_adjust(left=0, right=1, top=1, bottom=0)
157
-
158
- # 设置字体
159
  plt.rcParams['font.family'] = 'sans-serif'
160
- plt.rcParams['font.sans-serif'] = ['Arial Unicode MS'] # 确保字体可用
161
-
162
- # 创建网格,顶部为日期行,下方为数据行
163
- # 使用高度(英寸)作为比率,GridSpec会自动归一化
164
  gs = gridspec.GridSpec(
165
  num_rows + 1, NUM_COLS,
166
- hspace=0, wspace=0, # 无单元格间距
167
  height_ratios=[date_header_height_in] + [cell_height_in] * num_rows,
168
  figure=fig
169
  )
170
-
171
- # --- 3. 补全和排序数据 ---
172
  data_values = data.values.tolist()
173
  while len(data_values) % NUM_COLS != 0:
174
  data_values.append(['', ''])
175
-
176
  rows_per_col_layout = math.ceil(len(data_values) / NUM_COLS)
177
  sorted_data = [['', '']] * len(data_values)
178
  for i, item in enumerate(data_values):
@@ -182,109 +308,101 @@ def create_print_layout(data, title, date_str):
182
  new_index = row_in_col * NUM_COLS + col_idx
183
  if new_index < len(sorted_data):
184
  sorted_data[new_index] = item
185
-
186
- # --- 4. 绘制数据单元格 ---
187
  for idx, (hall, end_time) in enumerate(sorted_data):
188
  if hall and end_time:
189
- row_grid = idx // NUM_COLS + 1 # +1 因为日期行占了第0行
190
  col_grid = idx % NUM_COLS
191
-
192
  ax = fig.add_subplot(gs[row_grid, col_grid])
193
-
194
- # --- 设置点状虚线边框 ---
195
  for spine in ax.spines.values():
196
  spine.set_visible(True)
197
- spine.set_linestyle((0, (1, 2))) # 点状线: (offset, (on_length, off_length))
198
  spine.set_color(BORDER_COLOR)
199
- spine.set_linewidth(0.75) # 点状线可能需要稍粗一点才清晰
200
-
201
- # 绘制居中对齐的文本
202
  display_text = f"{hall}{end_time}"
203
  ax.text(0.5, 0.5, display_text,
204
  fontsize=base_fontsize,
205
  fontweight='bold',
206
  ha='center', va='center',
207
  transform=ax.transAxes)
208
-
209
  ax.set_xticks([])
210
  ax.set_yticks([])
211
  ax.set_facecolor('none')
212
-
213
- # --- 5. 绘制日期标题 ---
214
  ax_date = fig.add_subplot(gs[0, :])
215
  ax_date.text(0.01, 0.5, f"{date_str} {title}",
216
- fontsize=base_fontsize * 0.5, # 日期字体稍小
217
  color=DATE_COLOR, fontweight='bold',
218
  ha='left', va='center',
219
  transform=ax_date.transAxes)
220
- ax_date.set_axis_off() # 完全隐藏日期行的边框和刻度
221
  ax_date.set_facecolor('none')
222
-
223
  return fig
224
 
225
- # --- 生成并保存图形 ---
226
  fig_for_output = generate_figure()
227
-
228
- # 保存为 PNG
229
  png_buffer = io.BytesIO()
230
- fig_for_output.savefig(png_buffer, format='png') # 无需 bbox_inches='tight'
231
  png_buffer.seek(0)
232
  png_base64 = base64.b64encode(png_buffer.getvalue()).decode()
233
-
234
- # 保存为 PDF
235
  pdf_buffer = io.BytesIO()
236
  with PdfPages(pdf_buffer) as pdf:
237
- pdf.savefig(fig_for_output) # 无需 bbox_inches='tight'
238
  pdf_buffer.seek(0)
239
  pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode()
240
-
241
- plt.close(fig_for_output) # 关闭图形,释放内存
242
-
243
  return {
244
  'png': f'data:image/png;base64,{png_base64}',
245
  'pdf': f'data:application/pdf;base64,{pdf_base64}'
246
  }
247
 
248
-
249
  def display_pdf(base64_pdf):
250
- """Streamlit中嵌入显示PDF"""
251
- pdf_display = f'<iframe src="{base64_pdf}" width="100%" height="800" type="application/pdf"></iframe>'
252
- return pdf_display
253
 
254
- # --- Streamlit 界面 ---
255
- st.set_page_config(page_title="散厅时间快捷打印", layout="wide")
256
- st.title("散厅时间快捷打印")
257
 
258
- uploaded_file = st.file_uploader("上传【放映场次核对表.xls】文件", type=["xls"])
 
259
 
260
  if uploaded_file:
261
- part1, part2, date_str = process_schedule(uploaded_file)
262
-
263
- if part1 is not None and part2 is not None:
264
- # 生成包含 PNG 和 PDF 的字典
265
- part1_output = create_print_layout(part1, "A", date_str)
266
- part2_output = create_print_layout(part2, "C", date_str)
267
-
268
- col1, col2 = st.columns(2)
269
-
270
- with col1:
271
- st.subheader("白班散场预览(时间 ≤ 17:30)")
272
- if part1_output:
273
- tab1_1, tab1_2 = st.tabs(["PDF 预览", "PNG 预览"])
274
- with tab1_1:
275
- st.markdown(display_pdf(part1_output['pdf']), unsafe_allow_html=True)
276
- with tab1_2:
277
- st.image(part1_output['png'])
278
- else:
279
- st.info("白班部分没有数据")
280
-
281
- with col2:
282
- st.subheader("夜班散场预览(时间 > 17:30)")
283
- if part2_output:
284
- tab2_1, tab2_2 = st.tabs(["PDF 预览", "PNG 预览"])
285
- with tab2_1:
286
- st.markdown(display_pdf(part2_output['pdf']), unsafe_allow_html=True)
287
- with tab2_2:
288
- st.image(part2_output['png'])
289
  else:
290
- 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
+ from matplotlib.lines import Line2D
6
  import io
7
  import base64
8
+ import os
9
+ from datetime import datetime, timedelta
10
+ from pypinyin import lazy_pinyin, Style
11
+ from matplotlib.backends.backend_pdf import PdfPages
12
  import matplotlib.gridspec as gridspec
13
  import math
 
14
 
15
+ # --- Constants for the second script ---
16
  SPLIT_TIME = "17:30"
17
  BUSINESS_START = "09:30"
18
  BUSINESS_END = "01:30"
19
+ BORDER_COLOR = 'grey'
20
  DATE_COLOR = '#A9A9A9'
21
  A5_WIDTH_IN = 5.83
22
  A5_HEIGHT_IN = 8.27
23
  NUM_COLS = 3
24
 
25
+ # --- Functions from the first script ---
26
+ def get_font(size=14):
27
+ """Loads a specific TrueType font, defaulting to a common Chinese font."""
28
+ font_path = "simHei.ttc"
29
+ if not os.path.exists(font_path):
30
+ font_path = "SimHei.ttf"
31
+ if os.path.exists(font_path):
32
+ return font_manager.FontProperties(fname=font_path, size=size)
33
+ else:
34
+ st.warning("SimHei font not found. Display may not be correct. Please add simHei.ttc or SimHei.ttf.")
35
+ return font_manager.FontProperties(family='sans-serif', size=size)
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
+ chars = [c for c in text if '\u4e00' <= c <= '\u9fff'][:2]
42
+ if not chars:
43
+ return ""
44
+ pinyin_list = lazy_pinyin(chars, style=Style.FIRST_LETTER)
45
+ return ''.join(pinyin_list).upper()
46
+
47
+ def format_seq(n):
48
+ """Converts an integer to a circled number string (e.g., 1 -> ①)."""
49
+ if not isinstance(n, int) or n <= 0:
50
+ return str(n)
51
+ circled_chars = "①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳" \
52
+ "㉑㉒㉓㉔㉕㉖㉗㉘㉙㉚㉛㉜㉝㉞㉟" \
53
+ "㊱㊲㊳㊴㊵㊶㊷㊸㊹㊺㊻㊼㊽㊾㊿"
54
+ if 1 <= n <= 50:
55
+ return circled_chars[n - 1]
56
+ return f'({n})'
57
+
58
+ def process_schedule_led(file):
59
+ """Processes '放映时间核对表.xls'."""
60
+ try:
61
+ date_df = pd.read_excel(file, header=None, skiprows=7, nrows=1, usecols=[3])
62
+ date_str = pd.to_datetime(date_df.iloc[0, 0]).strftime('%Y-%m-%d')
63
+ base_date = pd.to_datetime(date_str).date()
64
+ except Exception:
65
+ date_str = datetime.today().strftime('%Y-%m-%d')
66
+ base_date = datetime.today().date()
67
 
 
 
68
  try:
69
+ df = pd.read_excel(file, header=9, usecols=[1, 2, 4, 5])
70
+ df.columns = ['Hall', 'StartTime', 'EndTime', 'Movie']
71
+ df['Hall'] = df['Hall'].ffill()
72
+ df.dropna(subset=['StartTime', 'EndTime', 'Movie'], inplace=True)
73
+ df['Hall'] = df['Hall'].astype(str).str.extract(r'(\d+号)')
74
+ df['StartTime_dt'] = pd.to_datetime(df['StartTime'], format='%H:%M', errors='coerce').apply(
75
+ lambda t: t.replace(year=base_date.year, month=base_date.month, day=base_date.day) if pd.notnull(t) else t
76
+ )
77
+ df['EndTime_dt'] = pd.to_datetime(df['EndTime'], format='%H:%M', errors='coerce').apply(
78
+ lambda t: t.replace(year=base_date.year, month=base_date.month, day=base_date.day) if pd.notnull(t) else t
79
+ )
80
+ df.loc[df['EndTime_dt'] < df['StartTime_dt'], 'EndTime_dt'] += timedelta(days=1)
81
+ df = df.sort_values(['Hall', 'StartTime_dt'])
82
+ merged_rows = []
83
+ for _, group in df.groupby('Hall'):
84
+ current = None
85
+ for _, row in group.sort_values('StartTime_dt').iterrows():
86
+ if current is None:
87
+ current = row.copy()
88
+ elif row['Movie'] == current['Movie']:
89
+ current['EndTime_dt'] = row['EndTime_dt']
90
+ else:
91
+ merged_rows.append(current)
92
+ current = row.copy()
93
+ if current is not None:
94
+ merged_rows.append(current)
95
+ merged_df = pd.DataFrame(merged_rows)
96
+ merged_df['StartTime_dt'] -= timedelta(minutes=10)
97
+ merged_df['EndTime_dt'] -= timedelta(minutes=5)
98
+ merged_df['Seq'] = merged_df.groupby('Hall').cumcount() + 1
99
+ merged_df['StartTime_str'] = merged_df['StartTime_dt'].dt.strftime('%H:%M')
100
+ merged_df['EndTime_str'] = merged_df['EndTime_dt'].dt.strftime('%H:%M')
101
+ return merged_df[['Hall', 'Seq', 'Movie', 'StartTime_str', 'EndTime_str']], date_str
102
+ except Exception as e:
103
+ st.error(f"Error processing schedule data: {e}. Please check the file format.")
104
+ return None, date_str
105
 
106
+ def create_print_layout_led(data, date_str):
107
+ """Generates print layouts for '放映时间核对表.xls'."""
108
+ if data is None or data.empty:
109
+ return None
110
+ A4_width_in, A4_height_in = 8.27, 11.69
111
+ dpi = 300
112
+ total_content_rows = len(data)
113
+ totalA = total_content_rows + 2
114
+ row_height = A4_height_in / totalA
115
+ data = data.reset_index(drop=True)
116
+ data['hall_str'] = '$' + data['Hall'].str.replace('号', '') + '^{\#}$'
117
+ data['seq_str'] = data['Seq'].apply(format_seq)
118
+ data['pinyin_abbr'] = data['Movie'].apply(get_pinyin_abbr)
119
+ data['time_str'] = data['StartTime_str'] + ' - ' + data['EndTime_str']
120
+ temp_fig = plt.figure(figsize=(A4_width_in, A4_height_in), dpi=dpi)
121
+ renderer = temp_fig.canvas.get_renderer()
122
+ base_font_size_pt = (row_height * 0.9) * 72
123
+ seq_font_size_pt = (row_height * 0.5) * 72
124
+
125
+ def get_col_width_in(series, font_size_pt, is_math=False):
126
+ if series.empty:
127
+ return 0
128
+ font_prop = get_font(font_size_pt)
129
+ longest_str_idx = series.astype(str).str.len().idxmax()
130
+ max_content = str(series.loc[longest_str_idx])
131
+ text_width_px, _, _ = renderer.get_text_width_height_descent(max_content, font_prop, ismath=is_math)
132
+ return (text_width_px / dpi) * 1.1
133
+
134
+ margin_col_width = row_height
135
+ hall_col_width = get_col_width_in(data['hall_str'], base_font_size_pt, is_math=True)
136
+ seq_col_width = get_col_width_in(data['seq_str'], seq_font_size_pt)
137
+ pinyin_col_width = get_col_width_in(data['pinyin_abbr'], base_font_size_pt)
138
+ time_col_width = get_col_width_in(data['time_str'], base_font_size_pt)
139
+ movie_col_width = A4_width_in - (
140
+ margin_col_width * 2 + hall_col_width + seq_col_width + pinyin_col_width + time_col_width)
141
+ plt.close(temp_fig)
142
+ col_widths = {'hall': hall_col_width, 'seq': seq_col_width, 'movie': movie_col_width, 'pinyin': pinyin_col_width,
143
+ 'time': time_col_width}
144
+ col_x_starts = {}
145
+ current_x = margin_col_width
146
+ for col_name in ['hall', 'seq', 'movie', 'pinyin', 'time']:
147
+ col_x_starts[col_name] = current_x
148
+ current_x += col_widths[col_name]
149
+
150
+ def draw_figure(fig, ax):
151
+ renderer = fig.canvas.get_renderer()
152
+ for col_name in ['hall', 'seq', 'movie', 'pinyin']:
153
+ x_line = col_x_starts[col_name] + col_widths[col_name]
154
+ line_top_y, line_bottom_y = A4_height_in - row_height, row_height
155
+ ax.add_line(
156
+ Line2D([x_line, x_line], [line_bottom_y, line_top_y], color='gray', linestyle=':', linewidth=0.5))
157
+ last_hall_drawn = None
158
+ for i, row in data.iterrows():
159
+ y_bottom = A4_height_in - (i + 2) * row_height
160
+ y_center = y_bottom + row_height / 2
161
+ if row['Hall'] != last_hall_drawn:
162
+ ax.text(col_x_starts['hall'] + col_widths['hall'] / 2, y_center, row['hall_str'],
163
+ fontproperties=get_font(base_font_size_pt), ha='center', va='center')
164
+ last_hall_drawn = row['Hall']
165
+ ax.text(col_x_starts['seq'] + col_widths['seq'] / 2, y_center, row['seq_str'],
166
+ fontproperties=get_font(seq_font_size_pt), ha='center', va='center')
167
+ ax.text(col_x_starts['pinyin'] + col_widths['pinyin'] / 2, y_center, row['pinyin_abbr'],
168
+ fontproperties=get_font(base_font_size_pt), ha='center', va='center')
169
+ ax.text(col_x_starts['time'] + col_widths['time'] / 2, y_center, row['time_str'],
170
+ fontproperties=get_font(base_font_size_pt), ha='center', va='center')
171
+ movie_font_size = base_font_size_pt
172
+ movie_font_prop = get_font(movie_font_size)
173
+ text_w_px, _, _ = renderer.get_text_width_height_descent(row['Movie'], movie_font_prop, ismath=False)
174
+ text_w_in = text_w_px / dpi
175
+ max_width_in = col_widths['movie'] * 0.9
176
+ if text_w_in > max_width_in:
177
+ movie_font_size *= (max_width_in / text_w_in)
178
+ movie_font_prop = get_font(movie_font_size)
179
+ ax.text(col_x_starts['movie'] + 0.05, y_center, row['Movie'],
180
+ fontproperties=movie_font_prop, ha='left', va='center')
181
+ is_last_in_hall = (i == len(data) - 1) or (row['Hall'] != data.loc[i + 1, 'Hall'])
182
+ line_start_x = margin_col_width
183
+ line_end_x = A4_width_in - margin_col_width
184
+ if is_last_in_hall:
185
+ ax.add_line(Line2D([line_start_x, line_end_x], [y_bottom, y_bottom], color='black', linestyle='-',
186
+ linewidth=0.8))
187
+ else:
188
+ ax.add_line(Line2D([line_start_x, line_end_x], [y_bottom, y_bottom], color='gray', linestyle=':',
189
+ linewidth=0.5))
190
+
191
+ outputs = {}
192
+ for format_type in ['png', 'pdf']:
193
+ fig = plt.figure(figsize=(A4_width_in, A4_height_in), dpi=dpi)
194
+ ax = fig.add_axes([0, 0, 1, 1])
195
+ ax.set_axis_off()
196
+ ax.set_xlim(0, A4_width_in)
197
+ ax.set_ylim(0, A4_height_in)
198
+ ax.text(margin_col_width, A4_height_in - (row_height / 2), date_str,
199
+ fontproperties=get_font(10), color='#A9A9A9', ha='left', va='center')
200
+ draw_figure(fig, ax)
201
+ buf = io.BytesIO()
202
+ fig.savefig(buf, format=format_type, dpi=dpi, bbox_inches='tight', pad_inches=0)
203
+ buf.seek(0)
204
+ data_uri = base64.b64encode(buf.getvalue()).decode()
205
+ mime_type = 'image/png' if format_type == 'png' else 'application/pdf'
206
+ outputs[format_type] = f"data:{mime_type};base64,{data_uri}"
207
+ plt.close(fig)
208
+ return outputs
209
+
210
+ # --- Functions from the second script ---
211
+ def process_schedule_times(file):
212
+ """Processes '放映场次核对表.xls'."""
213
+ try:
214
+ df = pd.read_excel(file, skiprows=8)
215
+ df = df.iloc[:, [6, 7, 9]]
216
  df.columns = ['Hall', 'StartTime', 'EndTime']
 
 
217
  df = df.dropna(subset=['Hall', 'StartTime', 'EndTime'])
 
 
218
  df['Hall'] = df['Hall'].str.extract(r'(\d+)号').astype(str) + ' '
 
 
219
  df['original_end'] = df['EndTime']
 
 
220
  base_date = datetime.today().date()
221
  df['StartTime'] = pd.to_datetime(df['StartTime'])
222
  df['EndTime'] = pd.to_datetime(df['EndTime'])
 
 
223
  business_start = datetime.strptime(f"{base_date} {BUSINESS_START}", "%Y-%m-%d %H:%M")
224
  business_end = datetime.strptime(f"{base_date} {BUSINESS_END}", "%Y-%m-%d %H:%M")
 
 
225
  if business_end < business_start:
226
  business_end += timedelta(days=1)
 
 
227
  for idx, row in df.iterrows():
228
  end_time = row['EndTime']
229
  if end_time.hour < 9:
230
  df.at[idx, 'EndTime'] = end_time + timedelta(days=1)
 
231
  if row['StartTime'].hour >= 21 and end_time.hour < 9:
232
  df.at[idx, 'EndTime'] = end_time + timedelta(days=1)
 
 
233
  df['time_for_comparison'] = df['EndTime'].apply(
234
  lambda x: datetime.combine(base_date, x.time())
235
  )
 
236
  df.loc[df['time_for_comparison'].dt.hour < 9, 'time_for_comparison'] += timedelta(days=1)
 
237
  valid_times = (
238
+ ((df['time_for_comparison'] >= datetime.combine(base_date, business_start.time())) &
239
+ (df['time_for_comparison'] <= datetime.combine(base_date + timedelta(days=1), business_end.time())))
240
  )
 
241
  df = df[valid_times]
 
 
242
  df = df.sort_values('EndTime')
 
 
243
  split_time = datetime.strptime(f"{base_date} {SPLIT_TIME}", "%Y-%m-%d %H:%M")
244
  split_time_for_comparison = df['time_for_comparison'].apply(
245
  lambda x: datetime.combine(base_date, split_time.time())
246
  )
 
247
  part1 = df[df['time_for_comparison'] <= split_time_for_comparison].copy()
248
  part2 = df[df['time_for_comparison'] > split_time_for_comparison].copy()
 
 
249
  for part in [part1, part2]:
250
  part['EndTime'] = part['EndTime'].dt.strftime('%-H:%M')
 
 
251
  date_df = pd.read_excel(
252
  file,
253
+ skiprows=5,
254
+ nrows=1,
255
+ usecols=[2],
256
+ header=None
257
  )
258
  date_cell = date_df.iloc[0, 0]
 
259
  try:
 
260
  if isinstance(date_cell, str):
261
  date_str = datetime.strptime(date_cell, '%Y-%m-%d').strftime('%Y-%m-%d')
262
  else:
263
  date_str = pd.to_datetime(date_cell).strftime('%Y-%m-%d')
264
  except:
265
  date_str = datetime.today().strftime('%Y-%m-%d')
 
266
  return part1[['Hall', 'EndTime']], part2[['Hall', 'EndTime']], date_str
 
267
  except Exception as e:
268
  st.error(f"处理文件时出错: {str(e)}")
269
  return None, None, None
270
 
271
+ def create_print_layout_times(data, title, date_str):
272
+ """Creates print layouts for '放映场次核对表.xls'."""
 
273
  if data.empty:
274
  return None
275
 
 
276
  def generate_figure():
 
277
  total_items = len(data)
278
  num_rows = math.ceil(total_items / NUM_COLS) if total_items > 0 else 1
 
 
279
  date_header_height_in = 0.3
280
  data_area_height_in = A5_HEIGHT_IN - date_header_height_in
 
 
281
  cell_width_in = A5_WIDTH_IN / NUM_COLS
282
  cell_height_in = data_area_height_in / num_rows
 
 
283
  cell_width_pt = cell_width_in * 72
284
  cell_height_pt = cell_height_in * 72
 
 
 
285
  target_text_width_pt = cell_width_pt * 0.9
 
 
286
  fontsize_from_width = target_text_width_pt / (8 * 0.6)
 
287
  fontsize_from_height = cell_height_pt * 0.8
 
288
  base_fontsize = min(fontsize_from_width, fontsize_from_height)
 
 
289
  fig = plt.figure(figsize=(A5_WIDTH_IN, A5_HEIGHT_IN), dpi=300)
 
290
  fig.subplots_adjust(left=0, right=1, top=1, bottom=0)
 
 
291
  plt.rcParams['font.family'] = 'sans-serif'
292
+ plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
 
 
 
293
  gs = gridspec.GridSpec(
294
  num_rows + 1, NUM_COLS,
295
+ hspace=0, wspace=0,
296
  height_ratios=[date_header_height_in] + [cell_height_in] * num_rows,
297
  figure=fig
298
  )
 
 
299
  data_values = data.values.tolist()
300
  while len(data_values) % NUM_COLS != 0:
301
  data_values.append(['', ''])
 
302
  rows_per_col_layout = math.ceil(len(data_values) / NUM_COLS)
303
  sorted_data = [['', '']] * len(data_values)
304
  for i, item in enumerate(data_values):
 
308
  new_index = row_in_col * NUM_COLS + col_idx
309
  if new_index < len(sorted_data):
310
  sorted_data[new_index] = item
 
 
311
  for idx, (hall, end_time) in enumerate(sorted_data):
312
  if hall and end_time:
313
+ row_grid = idx // NUM_COLS + 1
314
  col_grid = idx % NUM_COLS
 
315
  ax = fig.add_subplot(gs[row_grid, col_grid])
 
 
316
  for spine in ax.spines.values():
317
  spine.set_visible(True)
318
+ spine.set_linestyle((0, (1, 2)))
319
  spine.set_color(BORDER_COLOR)
320
+ spine.set_linewidth(0.75)
 
 
321
  display_text = f"{hall}{end_time}"
322
  ax.text(0.5, 0.5, display_text,
323
  fontsize=base_fontsize,
324
  fontweight='bold',
325
  ha='center', va='center',
326
  transform=ax.transAxes)
 
327
  ax.set_xticks([])
328
  ax.set_yticks([])
329
  ax.set_facecolor('none')
 
 
330
  ax_date = fig.add_subplot(gs[0, :])
331
  ax_date.text(0.01, 0.5, f"{date_str} {title}",
332
+ fontsize=base_fontsize * 0.5,
333
  color=DATE_COLOR, fontweight='bold',
334
  ha='left', va='center',
335
  transform=ax_date.transAxes)
336
+ ax_date.set_axis_off()
337
  ax_date.set_facecolor('none')
 
338
  return fig
339
 
 
340
  fig_for_output = generate_figure()
 
 
341
  png_buffer = io.BytesIO()
342
+ fig_for_output.savefig(png_buffer, format='png')
343
  png_buffer.seek(0)
344
  png_base64 = base64.b64encode(png_buffer.getvalue()).decode()
 
 
345
  pdf_buffer = io.BytesIO()
346
  with PdfPages(pdf_buffer) as pdf:
347
+ pdf.savefig(fig_for_output)
348
  pdf_buffer.seek(0)
349
  pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode()
350
+ plt.close(fig_for_output)
 
 
351
  return {
352
  'png': f'data:image/png;base64,{png_base64}',
353
  'pdf': f'data:application/pdf;base64,{pdf_base64}'
354
  }
355
 
 
356
  def display_pdf(base64_pdf):
357
+ """Generates the HTML to embed a PDF in Streamlit."""
358
+ return f'<iframe src="{base64_pdf}" width="100%" height="800" type="application/pdf"></iframe>'
 
359
 
360
+ # --- Streamlit App ---
361
+ st.set_page_config(page_title="Schedule Printer", layout="wide")
362
+ st.title("Schedule Printer")
363
 
364
+ uploaded_file = st.file_uploader("Select '放映时间核对表.xls' or '放映场次核对表.xls'",
365
+ type=["xls", "xlsx"])
366
 
367
  if uploaded_file:
368
+ if "时间" in uploaded_file.name:
369
+ st.header("LED Screen Schedule")
370
+ with st.spinner("Processing file, please wait..."):
371
+ schedule, date_str = process_schedule_led(uploaded_file)
372
+ if schedule is not None and not schedule.empty:
373
+ output = create_print_layout_led(schedule, date_str)
374
+ tab1, tab2 = st.tabs(["PDF Preview", "PNG Preview"])
375
+ with tab1:
376
+ st.markdown(display_pdf(output['pdf']), unsafe_allow_html=True)
377
+ with tab2:
378
+ st.image(output['png'], use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
379
  else:
380
+ st.error("Could not process the file. Please check if the file format and content are correct.")
381
+ elif "场次" in uploaded_file.name:
382
+ st.header("Screening Times Quick Print")
383
+ part1, part2, date_str = process_schedule_times(uploaded_file)
384
+ if part1 is not None and part2 is not None:
385
+ part1_output = create_print_layout_times(part1, "A", date_str)
386
+ part2_output = create_print_layout_times(part2, "C", date_str)
387
+ col1, col2 = st.columns(2)
388
+ with col1:
389
+ st.subheader("Day Shift (End time <= 17:30)")
390
+ if part1_output:
391
+ tab1_1, tab1_2 = st.tabs(["PDF Preview", "PNG Preview"])
392
+ with tab1_1:
393
+ st.markdown(display_pdf(part1_output['pdf']), unsafe_allow_html=True)
394
+ with tab1_2:
395
+ st.image(part1_output['png'])
396
+ else:
397
+ st.info("No data for the day shift.")
398
+ with col2:
399
+ st.subheader("Night Shift (End time > 17:30)")
400
+ if part2_output:
401
+ tab2_1, tab2_2 = st.tabs(["PDF Preview", "PNG Preview"])
402
+ with tab2_1:
403
+ st.markdown(display_pdf(part2_output['pdf']), unsafe_allow_html=True)
404
+ with tab2_2:
405
+ st.image(part2_output['png'])
406
+ else:
407
+ st.info("No data for the night shift.")
408
+