Ethscriptions commited on
Commit
1b1b918
·
verified ·
1 Parent(s): 928edc3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -96
app.py CHANGED
@@ -11,10 +11,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 font file, searching for common names."""
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):
@@ -23,23 +23,20 @@ def get_pinyin_abbr(text):
23
  return ""
24
  # Extract the first two Chinese characters
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
  # Get the first letter of the Pinyin
31
  pinyin_list = lazy_pinyin(chars, style=Style.FIRST_LETTER)
32
  return ''.join(pinyin_list).upper()
33
 
34
  def process_schedule(file):
35
- """Processes the uploaded Excel file to extract and clean schedule data."""
36
  try:
37
  # Try to read the date from a specific cell
38
  date_df = pd.read_excel(file, header=None, skiprows=7, nrows=1, usecols=[3])
39
  date_str = pd.to_datetime(date_df.iloc[0, 0]).strftime('%Y-%m-%d')
40
  base_date = pd.to_datetime(date_str).date()
41
- except:
42
- # Fallback to the current date if reading fails
43
  date_str = datetime.today().strftime('%Y-%m-%d')
44
  base_date = datetime.today().date()
45
 
@@ -47,6 +44,8 @@ def process_schedule(file):
47
  # Read the main schedule data
48
  df = pd.read_excel(file, header=9, usecols=[1, 2, 4, 5])
49
  df.columns = ['Hall', 'StartTime', 'EndTime', 'Movie']
 
 
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+号)')
@@ -58,6 +57,7 @@ def process_schedule(file):
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
  # 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'])
@@ -72,7 +72,7 @@ def process_schedule(file):
72
  current = row.copy()
73
  else:
74
  if row['Movie'] == current['Movie']:
75
- current['EndTime_dt'] = row['EndTime_dt']
76
  else:
77
  merged_rows.append(current)
78
  current = row.copy()
@@ -89,118 +89,116 @@ def process_schedule(file):
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:
 
93
  return None, date_str
94
 
95
  def create_print_layout(data, date_str):
96
- """Creates the PNG and PDF print layouts from the schedule data."""
97
  if data is None or data.empty:
98
  return None
99
-
100
- # Create PNG image for preview
 
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()
104
- png_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
105
-
106
- # Create PDF image for download
107
  pdf_fig = plt.figure(figsize=(8.27, 11.69), dpi=300)
108
  pdf_ax = pdf_fig.add_subplot(111)
109
  pdf_ax.set_axis_off()
110
- pdf_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
111
-
112
- def process_figure(fig, ax, is_pdf=False):
113
- """Shared function to draw the schedule on a matplotlib figure."""
114
- date_font = get_font(12)
115
-
116
- # Calculate row height to fill the page
117
- total_movies = len(data)
118
- # Add 2 to the total to create top and bottom margins
119
- total_divisions = total_movies + 2
120
-
121
- available_height_ax = 0.98 - 0.02 # Use 96% of the page height
122
- row_height_ax = available_height_ax / total_divisions
123
 
124
- # Convert relative height to point size for fonts
125
- fig_height_pts = fig.get_figheight() * 72
126
- row_height_pts = row_height_ax * fig_height_pts
127
 
128
- # Set font size to 90% of the calculated row height
129
- font_size = row_height_pts * 0.9
130
- hall_font_size = font_size * 0.8
 
131
 
132
- hall_font = get_font(hall_font_size)
133
- movie_font = get_font(font_size)
134
 
135
- # Add date stamp to the top left
136
- ax.text(0.00, 1.00, date_str, fontsize=12, color='#A9A9A9',
137
- ha='left', va='top', fontproperties=date_font, transform=ax.transAxes, zorder=2)
138
 
139
- halls = sorted(data['Hall'].unique(), key=lambda h: int(h.replace('号','')) if h else 0)
 
 
 
140
 
141
- # Start drawing from the top, leaving one row_height as a margin
142
- y_position = 0.98 - row_height_ax
 
 
 
 
 
 
143
 
144
  for i, hall in enumerate(halls):
145
  hall_data = data[data['Hall'] == hall]
146
- hall_num = hall.replace("", "")
147
- hall_text = f"${hall_num}^{{\\#}}$"
148
  movie_count = 1
149
 
 
 
 
 
 
150
  for _, row in hall_data.iterrows():
151
- # Hall Number (left-aligned)
152
- if movie_count == 1:
153
- ax.text(0.03, y_position, hall_text,
154
- fontsize=hall_font_size, fontweight='bold',
155
- ha='left', va='top', fontproperties=hall_font,
156
- transform=ax.transAxes, zorder=2)
157
-
158
  pinyin_abbr = get_pinyin_abbr(row['Movie'])
159
 
160
- # New content order and alignment
161
- # 1. Movie Name (right-aligned)
162
- ax.text(0.55, y_position, row['Movie'],
163
- fontsize=font_size, ha='right', va='top', fontproperties=movie_font,
164
- transform=ax.transAxes, zorder=2)
165
-
166
- # 2. Sequence Number (left-aligned)
167
- ax.text(0.57, y_position, f"{movie_count}.",
168
- fontsize=font_size, ha='left', va='top', fontproperties=movie_font,
169
- transform=ax.transAxes, zorder=2)
170
 
171
- # 3. Pinyin Abbreviation (left-aligned)
172
- ax.text(0.63, y_position, pinyin_abbr,
173
- fontsize=font_size, ha='left', va='top', fontproperties=movie_font,
174
- transform=ax.transAxes, zorder=2)
175
-
176
- # 4. Time (left-aligned)
177
- ax.text(0.72, y_position, f"{row['StartTime_str']} - {row['EndTime_str']}",
178
- fontsize=font_size, ha='left', va='top', fontproperties=movie_font,
179
- transform=ax.transAxes, zorder=2)
 
 
 
 
 
 
 
180
 
181
- y_position -= row_height_ax
182
  movie_count += 1
183
-
184
- # Draw a black line to separate halls
185
- if i < len(halls) - 1:
186
- line_y = y_position + (row_height_ax / 2)
187
- ax.plot([0.03, 0.97], [line_y, line_y], color='black', linewidth=1, transform=ax.transAxes, zorder=1)
188
 
189
- # Process both the PNG and PDF figures
 
 
 
 
 
 
 
190
  process_figure(png_fig, png_ax)
191
- process_figure(pdf_fig, pdf_ax, is_pdf=True)
192
 
193
- # Save PNG to buffer
194
  png_buffer = io.BytesIO()
195
  png_fig.savefig(png_buffer, format='png', bbox_inches='tight', pad_inches=0.05)
196
  png_buffer.seek(0)
197
  image_base64 = base64.b64encode(png_buffer.getvalue()).decode()
198
  plt.close(png_fig)
199
 
200
- # Save PDF to buffer
201
  pdf_buffer = io.BytesIO()
202
- with PdfPages(pdf_buffer) as pdf:
203
- pdf.savefig(pdf_fig, bbox_inches='tight', pad_inches=0.05)
204
  pdf_buffer.seek(0)
205
  pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode()
206
  plt.close(pdf_fig)
@@ -212,25 +210,22 @@ def create_print_layout(data, date_str):
212
 
213
  def display_pdf(base64_pdf):
214
  """Generates the HTML to embed a PDF in Streamlit."""
215
- pdf_display = f"""
216
- <iframe src="{base64_pdf}" width="100%" height="800" type="application/pdf"></iframe>
217
- """
218
- return pdf_display
219
 
220
- # --- Streamlit App ---
221
- st.set_page_config(page_title="LED 屏幕时间表打印", layout="wide")
222
- st.title("LED 屏幕时间表打印")
223
 
224
- uploaded_file = st.file_uploader("选择打开【放映时间核对表.xls】文件", accept_multiple_files=False, type=["xls"])
225
 
226
  if uploaded_file:
227
- with st.spinner("文件正在处理中,请稍候..."):
228
  schedule, date_str = process_schedule(uploaded_file)
229
  if schedule is not None:
230
  output = create_print_layout(schedule, date_str)
231
 
232
- # Create tabs for PDF and PNG previews
233
- tab1, tab2 = st.tabs(["PDF 预览", "PNG 预览"])
234
 
235
  with tab1:
236
  st.markdown(display_pdf(output['pdf']), unsafe_allow_html=True)
@@ -238,4 +233,4 @@ if uploaded_file:
238
  with tab2:
239
  st.image(output['png'], use_container_width=True)
240
  else:
241
- st.error("无法处理文件,请检查文件格式或内容是否正确。")
 
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):
 
23
  return ""
24
  # Extract the first two Chinese characters
25
  chars = [c for c in text if '\u4e00' <= c <= '\u9fff']
26
+ chars = chars[:2]
 
 
 
27
  # Get the first letter of the Pinyin
28
  pinyin_list = lazy_pinyin(chars, style=Style.FIRST_LETTER)
29
  return ''.join(pinyin_list).upper()
30
 
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()
38
+ except Exception:
39
+ # Fallback to today's date if reading fails
40
  date_str = datetime.today().strftime('%Y-%m-%d')
41
  base_date = datetime.today().date()
42
 
 
44
  # Read the main schedule data
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'])
 
72
  current = row.copy()
73
  else:
74
  if row['Movie'] == current['Movie']:
75
+ current['EndTime_dt'] = row['EndTime_dt'] # Extend the end time
76
  else:
77
  merged_rows.append(current)
78
  current = row.copy()
 
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 as e:
93
+ st.error(f"An error occurred during data processing: {e}")
94
  return None, date_str
95
 
96
  def create_print_layout(data, date_str):
97
+ """Creates PNG and PDF layouts for the schedule, designed to fill an A4 page."""
98
  if data is None or data.empty:
99
  return None
100
+
101
+ # --- Figure setup for both PNG and PDF ---
102
+ # We create two figures to handle potential differences in rendering pipelines
103
  png_fig = plt.figure(figsize=(8.27, 11.69), dpi=300)
104
  png_ax = png_fig.add_subplot(111)
105
  png_ax.set_axis_off()
106
+ png_fig.subplots_adjust(left=0.05, right=0.95, top=0.95, bottom=0.05)
107
+
 
108
  pdf_fig = plt.figure(figsize=(8.27, 11.69), dpi=300)
109
  pdf_ax = pdf_fig.add_subplot(111)
110
  pdf_ax.set_axis_off()
111
+ pdf_fig.subplots_adjust(left=0.05, right=0.95, top=0.95, bottom=0.05)
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
+ def process_figure(fig, ax):
114
+ """The core logic to draw the schedule on a given matplotlib axis."""
 
115
 
116
+ # --- Dynamic Calculation for Layout ---
117
+ halls = sorted(data['Hall'].unique(), key=lambda h: int(h.replace('号','')))
118
+ num_movies = len(data)
119
+ num_halls = len(halls)
120
 
121
+ # Total slots = one for each movie + one for each separator + 2 for padding
122
+ totalA = num_movies + (num_halls - 1) + 2
123
 
124
+ available_height = 0.95 # Usable portion of the page
125
+ line_height = available_height / totalA
 
126
 
127
+ # --- Dynamic Font Size Calculation ---
128
+ # Base font size is proportional to the line height to ensure it fits
129
+ # The factor (e.g., 60) is an empirical value that provides a good look
130
+ dynamic_font_size = line_height * 60
131
 
132
+ main_font = get_font(dynamic_font_size)
133
+ hall_font = get_font(dynamic_font_size * 1.2) # Make hall font slightly larger
134
+ date_font = get_font(12)
135
+
136
+ # Draw the date at the top
137
+ ax.text(0.01, 0.98, date_str, ha='left', va='top', fontproperties=date_font, color='#A9A9A9', transform=ax.transAxes)
138
+
139
+ y_position = 0.96 # Starting y-position from the top
140
 
141
  for i, hall in enumerate(halls):
142
  hall_data = data[data['Hall'] == hall]
143
+ hall_num_text = f"{hall.replace('', '')}"
 
144
  movie_count = 1
145
 
146
+ # Draw Hall Number (only once per hall block)
147
+ ax.text(0.03, y_position - (line_height / 2), hall_num_text,
148
+ fontsize=dynamic_font_size * 1.5, fontweight='bold', ha='center', va='center',
149
+ fontproperties=hall_font, transform=ax.transAxes)
150
+
151
  for _, row in hall_data.iterrows():
 
 
 
 
 
 
 
152
  pinyin_abbr = get_pinyin_abbr(row['Movie'])
153
 
154
+ # --- Content Layout per line ---
155
+ # Column positions are defined as fractions of the page width
156
+ x_movie_name = 0.48
157
+ x_movie_num = 0.1
158
+ x_pinyin = 0.18
159
+ x_time = 0.82
 
 
 
 
160
 
161
+ # 1. Movie Name (Right-aligned in its zone)
162
+ ax.text(x_movie_name, y_position, row['Movie'],
163
+ ha='right', va='center', fontproperties=main_font, transform=ax.transAxes)
164
+
165
+ # 2. Sequence Number (Left-aligned)
166
+ ax.text(x_movie_num, y_position, f"{movie_count}.",
167
+ ha='left', va='center', fontproperties=main_font, transform=ax.transAxes)
168
+
169
+ # 3. Pinyin Abbreviation (Left-aligned)
170
+ ax.text(x_pinyin, y_position, pinyin_abbr,
171
+ ha='left', va='center', fontproperties=main_font, transform=ax.transAxes)
172
+
173
+ # 4. Time (Left-aligned)
174
+ time_str = f"{row['StartTime_str']} - {row['EndTime_str']}"
175
+ ax.text(x_time, y_position, time_str,
176
+ ha='left', va='center', fontproperties=main_font, transform=ax.transAxes)
177
 
178
+ y_position -= line_height
179
  movie_count += 1
 
 
 
 
 
180
 
181
+ # Add a separator line after each hall block, except for the last one
182
+ if i < num_halls - 1:
183
+ y_position -= (line_height / 2) # small gap for the line
184
+ ax.axhline(y=y_position, xmin=0.02, xmax=0.98, color='black', linewidth=0.8)
185
+ y_position -= (line_height / 2) # gap after the line
186
+
187
+ # --- Generate Outputs ---
188
+ # Process both figures with the same logic
189
  process_figure(png_fig, png_ax)
190
+ process_figure(pdf_fig, pdf_ax)
191
 
192
+ # Save PNG to a 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 buffer
200
  pdf_buffer = io.BytesIO()
201
+ pdf_fig.savefig(pdf_buffer, format='pdf', bbox_inches='tight', pad_inches=0.05)
 
202
  pdf_buffer.seek(0)
203
  pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode()
204
  plt.close(pdf_fig)
 
210
 
211
  def display_pdf(base64_pdf):
212
  """Generates the HTML to embed a PDF in Streamlit."""
213
+ return f'<iframe src="{base64_pdf}" width="100%" height="800" type="application/pdf"></iframe>'
 
 
 
214
 
215
+ # --- Streamlit App Main ---
216
+ st.set_page_config(page_title="LED Screen Schedule Printer", layout="wide")
217
+ st.title("LED Screen Schedule Printer")
218
 
219
+ uploaded_file = st.file_uploader("Select '放映时间核对表.xls' file", type=["xls"])
220
 
221
  if uploaded_file:
222
+ with st.spinner("Processing file, please wait..."):
223
  schedule, date_str = process_schedule(uploaded_file)
224
  if schedule is not None:
225
  output = create_print_layout(schedule, date_str)
226
 
227
+ # Use tabs for PDF and PNG previews
228
+ tab1, tab2 = st.tabs(["PDF Preview", "PNG Preview"])
229
 
230
  with tab1:
231
  st.markdown(display_pdf(output['pdf']), unsafe_allow_html=True)
 
233
  with tab2:
234
  st.image(output['png'], use_container_width=True)
235
  else:
236
+ st.error("Could not process the file. Please check if the file format and content are correct.")