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

Update app.py

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