Ethscriptions commited on
Commit
7c5a0e0
·
verified ·
1 Parent(s): 2471374

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +88 -80
app.py CHANGED
@@ -6,19 +6,19 @@ import io
6
  import base64
7
  import os
8
  from datetime import datetime, timedelta
9
- from matplotlib.backends.backend_pdf import PdfPages
10
  from pypinyin import lazy_pinyin, Style
 
11
 
12
  def get_font(size=14):
13
- """Loads the SimHei font for Chinese character support."""
14
- # Prioritize 'simHei.ttc' if it exists, otherwise fall back to 'SimHei.ttf'
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
- """Gets the first letter of the Pinyin for the first two Chinese characters of a text."""
22
  if not text:
23
  return ""
24
  # Extract the first two Chinese characters
@@ -31,7 +31,7 @@ def get_pinyin_abbr(text):
31
  def process_schedule(file):
32
  """Processes the uploaded Excel file to extract and clean movie schedule data."""
33
  try:
34
- # Try to read the date from a specific cell
35
  date_df = pd.read_excel(file, header=None, skiprows=7, nrows=1, usecols=[3])
36
  date_str = pd.to_datetime(date_df.iloc[0, 0]).strftime('%Y-%m-%d')
37
  base_date = pd.to_datetime(date_str).date()
@@ -45,7 +45,7 @@ def process_schedule(file):
45
  df = pd.read_excel(file, header=9, usecols=[1, 2, 4, 5])
46
  df.columns = ['Hall', 'StartTime', 'EndTime', 'Movie']
47
 
48
- # Data cleaning
49
  df['Hall'] = df['Hall'].ffill()
50
  df.dropna(subset=['StartTime', 'EndTime', 'Movie'], inplace=True)
51
  df['Hall'] = df['Hall'].astype(str).str.extract(r'(\d+号)')
@@ -57,6 +57,7 @@ def process_schedule(file):
57
  df['EndTime_dt'] = pd.to_datetime(df['EndTime'], format='%H:%M', errors='coerce').apply(
58
  lambda t: t.replace(year=base_date.year, month=base_date.month, day=base_date.day) if pd.notnull(t) else t
59
  )
 
60
  # Handle overnight screenings
61
  df.loc[df['EndTime_dt'] < df['StartTime_dt'], 'EndTime_dt'] += timedelta(days=1)
62
  df = df.sort_values(['Hall', 'StartTime_dt'])
@@ -69,35 +70,34 @@ def process_schedule(file):
69
  for _, row in group.iterrows():
70
  if current is None:
71
  current = row.copy()
 
 
72
  else:
73
- if row['Movie'] == current['Movie']:
74
- current['EndTime_dt'] = row['EndTime_dt'] # Extend the end time
75
- else:
76
- merged_rows.append(current)
77
- current = row.copy()
78
  if current is not None:
79
  merged_rows.append(current)
80
 
81
  merged_df = pd.DataFrame(merged_rows)
82
 
83
- # Adjust times: start 10 mins earlier, end 5 mins earlier
84
  merged_df['StartTime_dt'] = merged_df['StartTime_dt'] - timedelta(minutes=10)
85
  merged_df['EndTime_dt'] = merged_df['EndTime_dt'] - timedelta(minutes=5)
86
 
87
- # Format times back to strings
88
  merged_df['StartTime_str'] = merged_df['StartTime_dt'].dt.strftime('%H:%M')
89
  merged_df['EndTime_str'] = merged_df['EndTime_dt'].dt.strftime('%H:%M')
90
 
91
  return merged_df[['Hall', 'Movie', 'StartTime_str', 'EndTime_str']], date_str
92
- except Exception:
 
93
  return None, date_str
94
 
95
  def create_print_layout(data, date_str):
96
- """Generates the print layout as PNG and PDF files based on the schedule data."""
97
  if data is None or data.empty:
98
  return None
99
-
100
- # Create figures for PNG and PDF output
101
  png_fig = plt.figure(figsize=(8.27, 11.69), dpi=300)
102
  png_ax = png_fig.add_subplot(111)
103
  png_ax.set_axis_off()
@@ -107,91 +107,98 @@ def create_print_layout(data, date_str):
107
  pdf_ax = pdf_fig.add_subplot(111)
108
  pdf_ax.set_axis_off()
109
  pdf_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
110
-
111
  def process_figure(fig, ax):
112
- """A helper function to draw the schedule on a given matplotlib Axes object."""
113
- halls = sorted(data['Hall'].unique(), key=lambda h: int(h.replace('号','')) if h else 0)
 
 
 
 
 
 
 
 
114
 
115
- # --- Dynamic Row and Font Size Calculation ---
116
- total_movie_lines = len(data)
117
- if total_movie_lines == 0:
118
- return
119
-
120
- num_halls = len(halls)
121
- num_separators = num_halls - 1 if num_halls > 1 else 0
122
-
123
- # Total vertical slots include movies, separators, and padding
124
- num_slots = total_movie_lines + num_separators + 2 # "+2" for top/bottom padding
125
-
126
- ax_top = 0.98
127
- ax_bottom = 0.02
128
- ax_height_fraction = ax_top - ax_bottom
129
- fig_height_inches = 11.69
130
 
131
- # Calculate font size to be 90% of the calculated row height
132
- slot_height_points = (fig_height_inches * ax_height_fraction * 72) / num_slots
133
- font_size = slot_height_points * 0.9
134
-
135
- content_font = get_font(font_size)
136
- hall_font = get_font(font_size * 1.1) # Hall font is slightly larger
137
  date_font = get_font(12)
138
 
139
- # --- Drawing Logic ---
140
- ax.text(0.0, 1.0, date_str, color='#A9A9A9',
141
  ha='left', va='top', fontproperties=date_font, transform=ax.transAxes)
142
-
143
- line_height = ax_height_fraction / num_slots
144
- y_position = ax_top - line_height # Start after top padding
145
-
146
  for i, hall in enumerate(halls):
147
  hall_data = data[data['Hall'] == hall]
148
- hall_num = hall.replace("", "")
149
 
150
- # Use a flag to print the hall number only once per hall
151
- is_first_movie_in_hall = True
152
  movie_count = 1
153
-
154
  for _, row in hall_data.iterrows():
155
- # Display Hall Number once for the block, on the same line as the first movie
156
- if is_first_movie_in_hall:
157
- ax.text(0.03, y_position, f"${hall_num}^{{\\#}}$",
158
- fontweight='bold', ha='left', va='top',
159
- fontproperties=hall_font, transform=ax.transAxes, zorder=2)
160
- is_first_movie_in_hall = False
161
-
162
- # --- New Content Layout ---
163
- # Left-aligned content: Seq. Number, Pinyin, Time
164
- ax.text(0.12, y_position, f"{movie_count}.", fontproperties=content_font, ha='left', va='top', transform=ax.transAxes)
165
- ax.text(0.18, y_position, get_pinyin_abbr(row['Movie']), fontproperties=content_font, ha='left', va='top', transform=ax.transAxes)
166
- ax.text(0.28, y_position, f"{row['StartTime_str']} - {row['EndTime_str']}", fontproperties=content_font, ha='left', va='top', transform=ax.transAxes)
167
-
168
- # Right-aligned content: Movie Name
169
- ax.text(0.97, y_position, row['Movie'], fontproperties=content_font, ha='right', va='top', transform=ax.transAxes, clip_on=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
  y_position -= line_height
172
  movie_count += 1
173
 
174
- # --- Separator Line ---
175
  if i < num_separators:
176
- # The line is drawn in the middle of the separator's allocated slot
177
- line_y = y_position + (line_height / 2)
178
- ax.axhline(y=line_y, color='black', linewidth=0.8, xmin=0.03, xmax=0.97)
179
- y_position -= line_height # Move down past the separator slot
180
 
181
- # Process both the PNG and PDF figures
182
  process_figure(png_fig, png_ax)
183
  process_figure(pdf_fig, pdf_ax)
184
-
185
- # Save PNG to a buffer
186
  png_buffer = io.BytesIO()
187
  png_fig.savefig(png_buffer, format='png', bbox_inches='tight', pad_inches=0.05)
188
  png_buffer.seek(0)
189
  image_base64 = base64.b64encode(png_buffer.getvalue()).decode()
190
  plt.close(png_fig)
191
 
192
- # Save PDF to a buffer
193
  pdf_buffer = io.BytesIO()
194
- with PdfPages(pdf_buffer, 'w') 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()
@@ -204,13 +211,14 @@ def create_print_layout(data, date_str):
204
 
205
  def display_pdf(base64_pdf):
206
  """Embeds the PDF in the Streamlit app for display."""
207
- return f'<iframe src="{base64_pdf}" width="100%" height="800" type="application/pdf"></iframe>'
 
208
 
209
- # --- Streamlit App UI ---
210
  st.set_page_config(page_title="LED 屏幕时间表打印", layout="wide")
211
  st.title("LED 屏幕时间表打印")
212
 
213
- uploaded_file = st.file_uploader("选择打开【放映时间核对表.xls】文件", type=["xls"])
214
 
215
  if uploaded_file:
216
  with st.spinner("文件正在处理中,请稍候..."):
 
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
 
13
  def get_font(size=14):
14
+ """Loads a specific font file, falling back to a default if not found."""
 
15
  font_path = "simHei.ttc"
16
  if not os.path.exists(font_path):
17
+ font_path = "SimHei.ttf" # Fallback font
18
  return font_manager.FontProperties(fname=font_path, size=size)
19
 
20
  def get_pinyin_abbr(text):
21
+ """Generates a two-letter pinyin abbreviation from the first two Chinese characters of a text."""
22
  if not text:
23
  return ""
24
  # Extract the first two Chinese characters
 
31
  def process_schedule(file):
32
  """Processes the uploaded Excel file to extract and clean movie schedule data."""
33
  try:
34
+ # Attempt to read the date from the Excel file
35
  date_df = pd.read_excel(file, header=None, skiprows=7, nrows=1, usecols=[3])
36
  date_str = pd.to_datetime(date_df.iloc[0, 0]).strftime('%Y-%m-%d')
37
  base_date = pd.to_datetime(date_str).date()
 
45
  df = pd.read_excel(file, header=9, usecols=[1, 2, 4, 5])
46
  df.columns = ['Hall', 'StartTime', 'EndTime', 'Movie']
47
 
48
+ # Clean and process the data
49
  df['Hall'] = df['Hall'].ffill()
50
  df.dropna(subset=['StartTime', 'EndTime', 'Movie'], inplace=True)
51
  df['Hall'] = df['Hall'].astype(str).str.extract(r'(\d+号)')
 
57
  df['EndTime_dt'] = pd.to_datetime(df['EndTime'], format='%H:%M', errors='coerce').apply(
58
  lambda t: t.replace(year=base_date.year, month=base_date.month, day=base_date.day) if pd.notnull(t) else t
59
  )
60
+
61
  # Handle overnight screenings
62
  df.loc[df['EndTime_dt'] < df['StartTime_dt'], 'EndTime_dt'] += timedelta(days=1)
63
  df = df.sort_values(['Hall', 'StartTime_dt'])
 
70
  for _, row in group.iterrows():
71
  if current is None:
72
  current = row.copy()
73
+ elif row['Movie'] == current['Movie']:
74
+ current['EndTime_dt'] = row['EndTime_dt']
75
  else:
76
+ merged_rows.append(current)
77
+ current = row.copy()
 
 
 
78
  if current is not None:
79
  merged_rows.append(current)
80
 
81
  merged_df = pd.DataFrame(merged_rows)
82
 
83
+ # Adjust start and end times
84
  merged_df['StartTime_dt'] = merged_df['StartTime_dt'] - timedelta(minutes=10)
85
  merged_df['EndTime_dt'] = merged_df['EndTime_dt'] - timedelta(minutes=5)
86
 
 
87
  merged_df['StartTime_str'] = merged_df['StartTime_dt'].dt.strftime('%H:%M')
88
  merged_df['EndTime_str'] = merged_df['EndTime_dt'].dt.strftime('%H:%M')
89
 
90
  return merged_df[['Hall', 'Movie', 'StartTime_str', 'EndTime_str']], date_str
91
+ except Exception as e:
92
+ st.error(f"Error processing file: {e}")
93
  return None, date_str
94
 
95
  def create_print_layout(data, date_str):
96
+ """Creates the PNG and PDF print layouts from the processed schedule data."""
97
  if data is None or data.empty:
98
  return None
99
+
100
+ # --- Figure Setup for PNG and PDF ---
101
  png_fig = plt.figure(figsize=(8.27, 11.69), dpi=300)
102
  png_ax = png_fig.add_subplot(111)
103
  png_ax.set_axis_off()
 
107
  pdf_ax = pdf_fig.add_subplot(111)
108
  pdf_ax.set_axis_off()
109
  pdf_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
110
+
111
  def process_figure(fig, ax):
112
+ """Helper function to draw the schedule on a given matplotlib figure axis."""
113
+ # --- Dynamic Height and Font Size Calculation ---
114
+ halls = sorted(data['Hall'].unique(), key=lambda h: int(h.replace('号', '')))
115
+ num_movies = len(data)
116
+ num_separators = len(halls) - 1 if len(halls) > 1 else 0
117
+
118
+ # Total vertical slots = movies + separators + 2 for padding
119
+ total_slots = num_movies + num_separators + 2
120
+ available_height = 0.96 # Usable page height (0.98 top - 0.02 bottom)
121
+ line_height = available_height / total_slots
122
 
123
+ # Convert figure line height to font points (72 points per inch)
124
+ fig_height_inches = fig.get_figheight()
125
+ font_size_pt = (fig_height_inches * line_height) * 72 * 0.90 # 90% of line height
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
+ movie_font = get_font(font_size_pt)
128
+ hall_font = get_font(font_size_pt) # Use the same size for impact
 
 
 
 
129
  date_font = get_font(12)
130
 
131
+ ax.text(0.01, 0.99, date_str, fontsize=12, color='#A9A9A9',
 
132
  ha='left', va='top', fontproperties=date_font, transform=ax.transAxes)
133
+
134
+ y_position = 0.98
135
+
 
136
  for i, hall in enumerate(halls):
137
  hall_data = data[data['Hall'] == hall]
138
+ hall_num_text = f"${hall.replace('', '')}^{{\\#}}$" # Format hall number with #
139
 
 
 
140
  movie_count = 1
 
141
  for _, row in hall_data.iterrows():
142
+ # --- Content Layout and Alignment ---
143
+ # Hall number is printed only for the first movie of the hall
144
+ if movie_count == 1:
145
+ ax.text(0.03, y_position, hall_num_text,
146
+ fontsize=font_size_pt, fontweight='bold', ha='left', va='top',
147
+ fontproperties=hall_font, transform=ax.transAxes)
148
+
149
+ pinyin_abbr = get_pinyin_abbr(row['Movie'])
150
+ time_str = f"{row['StartTime_str']} - {row['EndTime_str']}"
151
+
152
+ # Define x-coordinates for each column
153
+ x_movie_name_end = 0.48
154
+ x_seq_no_start = 0.51
155
+ x_pinyin_start = 0.62
156
+ x_time_start = 0.75
157
+
158
+ # Movie Name (Right-aligned)
159
+ ax.text(x_movie_name_end, y_position, row['Movie'],
160
+ fontsize=font_size_pt, ha='right', va='top',
161
+ fontproperties=movie_font, transform=ax.transAxes)
162
+
163
+ # Sequence Number (Left-aligned)
164
+ ax.text(x_seq_no_start, y_position, f"{movie_count}.",
165
+ fontsize=font_size_pt, ha='left', va='top',
166
+ fontproperties=movie_font, transform=ax.transAxes)
167
+
168
+ # Pinyin Abbreviation (Left-aligned)
169
+ ax.text(x_pinyin_start, y_position, pinyin_abbr,
170
+ fontsize=font_size_pt, ha='left', va='top',
171
+ fontproperties=movie_font, transform=ax.transAxes)
172
+
173
+ # Time (Left-aligned)
174
+ ax.text(x_time_start, y_position, time_str,
175
+ fontsize=font_size_pt, ha='left', va='top',
176
+ fontproperties=movie_font, transform=ax.transAxes)
177
 
178
  y_position -= line_height
179
  movie_count += 1
180
 
181
+ # --- Hall Separator Line ---
182
  if i < num_separators:
183
+ # Draw a black line to separate halls
184
+ ax.axhline(y=y_position + (line_height / 2), xmin=0.02, xmax=0.98,
185
+ color='black', linewidth=0.8, transform=ax.transAxes)
186
+ y_position -= line_height # Add space for the separator line
187
 
188
+ # Generate both PNG and PDF outputs
189
  process_figure(png_fig, png_ax)
190
  process_figure(pdf_fig, pdf_ax)
191
+
192
+ # Save PNG to a memory buffer
193
  png_buffer = io.BytesIO()
194
  png_fig.savefig(png_buffer, format='png', bbox_inches='tight', pad_inches=0.05)
195
  png_buffer.seek(0)
196
  image_base64 = base64.b64encode(png_buffer.getvalue()).decode()
197
  plt.close(png_fig)
198
 
199
+ # Save PDF to a memory buffer
200
  pdf_buffer = io.BytesIO()
201
+ with PdfPages(pdf_buffer) as pdf:
202
  pdf.savefig(pdf_fig, bbox_inches='tight', pad_inches=0.05)
203
  pdf_buffer.seek(0)
204
  pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode()
 
211
 
212
  def display_pdf(base64_pdf):
213
  """Embeds the PDF in the Streamlit app for display."""
214
+ pdf_display = f'<iframe src="{base64_pdf}" width="100%" height="800" type="application/pdf"></iframe>'
215
+ return pdf_display
216
 
217
+ # --- Streamlit App Main Interface ---
218
  st.set_page_config(page_title="LED 屏幕时间表打印", layout="wide")
219
  st.title("LED 屏幕时间表打印")
220
 
221
+ uploaded_file = st.file_uploader("选择打开【放映时间核对表.xls】文件", accept_multiple_files=False, type=["xls"])
222
 
223
  if uploaded_file:
224
  with st.spinner("文件正在处理中,请稍候..."):