Ethscriptions commited on
Commit
093d429
·
verified ·
1 Parent(s): 1b1b918

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +87 -92
app.py CHANGED
@@ -14,7 +14,7 @@ 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):
@@ -24,41 +24,36 @@ def get_pinyin_abbr(text):
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
 
43
  try:
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+号)')
52
 
53
- # Convert times to datetime objects
54
  df['StartTime_dt'] = pd.to_datetime(df['StartTime'], format='%H:%M', errors='coerce').apply(
55
  lambda t: t.replace(year=base_date.year, month=base_date.month, day=base_date.day) if pd.notnull(t) else t
56
  )
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'])
64
 
@@ -72,7 +67,7 @@ def process_schedule(file):
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()
@@ -90,102 +85,98 @@ def process_schedule(file):
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
 
@@ -198,7 +189,8 @@ def create_print_layout(data, date_str):
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)
@@ -209,23 +201,26 @@ def create_print_layout(data, date_str):
209
  }
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,4 +228,4 @@ if uploaded_file:
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.")
 
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):
 
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 for each character
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
+ # Attempt 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 the current date if reading fails
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
 
50
+ # Convert times to datetime objects, handling overnight screenings
51
  df['StartTime_dt'] = pd.to_datetime(df['StartTime'], format='%H:%M', errors='coerce').apply(
52
  lambda t: t.replace(year=base_date.year, month=base_date.month, day=base_date.day) if pd.notnull(t) else t
53
  )
54
  df['EndTime_dt'] = pd.to_datetime(df['EndTime'], format='%H:%M', errors='coerce').apply(
55
  lambda t: t.replace(year=base_date.year, month=base_date.month, day=base_date.day) if pd.notnull(t) else t
56
  )
 
 
57
  df.loc[df['EndTime_dt'] < df['StartTime_dt'], 'EndTime_dt'] += timedelta(days=1)
58
  df = df.sort_values(['Hall', 'StartTime_dt'])
59
 
 
67
  current = row.copy()
68
  else:
69
  if row['Movie'] == current['Movie']:
70
+ current['EndTime_dt'] = row['EndTime_dt']
71
  else:
72
  merged_rows.append(current)
73
  current = row.copy()
 
85
 
86
  return merged_df[['Hall', 'Movie', 'StartTime_str', 'EndTime_str']], date_str
87
  except Exception as e:
88
+ st.error(f"An error occurred during file processing: {e}")
89
  return None, date_str
90
 
91
  def create_print_layout(data, date_str):
92
+ """Generates PNG and PDF layouts of the schedule based on the provided data."""
93
  if data is None or data.empty:
94
  return None
95
 
96
+ # Create figures for PNG and PDF output with A4 dimensions
 
97
  png_fig = plt.figure(figsize=(8.27, 11.69), dpi=300)
98
  png_ax = png_fig.add_subplot(111)
99
  png_ax.set_axis_off()
100
+ png_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
101
+
102
  pdf_fig = plt.figure(figsize=(8.27, 11.69), dpi=300)
103
  pdf_ax = pdf_fig.add_subplot(111)
104
  pdf_ax.set_axis_off()
105
+ pdf_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
106
+
107
  def process_figure(fig, ax):
108
+ halls = sorted(data['Hall'].unique(), key=lambda h: int(h.replace('号','')) if h else 0)
109
+
110
+ # 1. A4 as a table: Calculate row height based on content
111
+ num_separators = len(halls) - 1
112
+ # Each movie, separator, and top/bottom margin gets a row
113
+ total_layout_rows = len(data) + num_separators + 2
 
 
 
 
 
 
114
 
115
+ available_height = 0.96 # Effective printable area (1.0 - top_margin - bottom_margin)
116
+ row_height = available_height / total_layout_rows
117
+
118
+ # Font height is 90% of the row height
119
+ fig_height_inches = fig.get_figheight()
120
+ row_height_points = row_height * fig_height_inches * 72 # Convert figure coords to points
121
+ font_size = row_height_points * 0.9
122
+
123
+ # Define fonts
124
+ date_font = get_font(font_size * 0.8)
125
+ hall_font = get_font(font_size)
126
+ movie_font = get_font(font_size)
127
+
128
+ # Display date at the top left
129
+ ax.text(0.02, 0.99, date_str, color='#A9A9A9',
130
+ ha='left', va='top', fontproperties=date_font, transform=ax.transAxes)
131
 
132
+ # Set starting Y position after top margin
133
+ y_position = 0.98 - row_height
 
 
 
 
134
 
135
+ # Define X positions for columns
136
+ col_hall_left = 0.03
137
+ col_movie_right = 0.50
138
+ col_seq_left = 0.52
139
+ col_pinyin_left = 0.62
140
+ col_time_left = 0.75
141
 
142
  for i, hall in enumerate(halls):
143
  hall_data = data[data['Hall'] == hall]
 
 
144
 
145
+ # 2. Add a separator line between halls (not before the first one)
146
+ if i > 0:
147
+ ax.axhline(y=y_position + row_height / 2, xmin=col_hall_left, xmax=0.97, color='black', linewidth=0.7)
148
+ y_position -= row_height # Move down one row for the separator space
149
 
150
+ movie_count = 1
151
  for _, row in hall_data.iterrows():
152
+ # Hall number (left-aligned), shown only for the first movie in the hall
153
+ if movie_count == 1:
154
+ ax.text(col_hall_left, y_position, f"{hall.replace('号', '')}#",
155
+ ha='left', va='center', fontweight='bold',
156
+ fontproperties=hall_font, transform=ax.transAxes)
 
 
 
157
 
158
+ # 3 & 4. New content order and alignment
159
+ # Column: Movie Name (Right-aligned)
160
+ ax.text(col_movie_right, y_position, row['Movie'],
161
+ ha='right', va='center', fontproperties=movie_font, transform=ax.transAxes)
162
+
163
+ # Column: Sequence Number (Left-aligned)
164
+ ax.text(col_seq_left, y_position, f"{movie_count}.",
165
+ ha='left', va='center', fontproperties=movie_font, transform=ax.transAxes)
166
+
167
+ # Column: Pinyin Abbreviation (Left-aligned)
168
+ pinyin_abbr = get_pinyin_abbr(row['Movie'])
169
+ ax.text(col_pinyin_left, y_position, pinyin_abbr,
170
+ ha='left', va='center', fontproperties=movie_font, transform=ax.transAxes)
 
 
 
171
 
172
+ # Column: Time (Left-aligned)
173
+ ax.text(col_time_left, y_position, f"{row['StartTime_str']}-{row['EndTime_str']}",
174
+ ha='left', va='center', fontproperties=movie_font, transform=ax.transAxes)
175
 
176
+ y_position -= row_height # Move to the next row
177
+ movie_count += 1
 
 
 
178
 
179
+ # Process both the PNG and PDF figures with the new layout logic
 
180
  process_figure(png_fig, png_ax)
181
  process_figure(pdf_fig, pdf_ax)
182
 
 
189
 
190
  # Save PDF to a buffer
191
  pdf_buffer = io.BytesIO()
192
+ with PdfPages(pdf_buffer) as pdf:
193
+ pdf.savefig(pdf_fig, bbox_inches='tight', pad_inches=0.05)
194
  pdf_buffer.seek(0)
195
  pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode()
196
  plt.close(pdf_fig)
 
201
  }
202
 
203
  def display_pdf(base64_pdf):
204
+ """Generates the HTML to embed and display a PDF in Streamlit."""
205
+ pdf_display = f"""
206
+ <iframe src="{base64_pdf}" width="100%" height="800" type="application/pdf"></iframe>
207
+ """
208
+ return pdf_display
209
 
210
+ # --- Streamlit App ---
211
+ st.set_page_config(page_title="LED 屏幕时间表打印", layout="wide")
212
+ st.title("LED 屏幕时间表打印")
213
 
214
+ uploaded_file = st.file_uploader("选择打开【放映时间核对表.xls】文件", accept_multiple_files=False, type=["xls"])
215
 
216
  if uploaded_file:
217
+ with st.spinner("文件正在处理中,请稍候..."):
218
  schedule, date_str = process_schedule(uploaded_file)
219
  if schedule is not None:
220
  output = create_print_layout(schedule, date_str)
221
 
222
+ # Create tabs for PDF and PNG previews
223
+ tab1, tab2 = st.tabs(["PDF 预览", "PNG 预览"])
224
 
225
  with tab1:
226
  st.markdown(display_pdf(output['pdf']), unsafe_allow_html=True)
 
228
  with tab2:
229
  st.image(output['png'], use_container_width=True)
230
  else:
231
+ st.error("无法处理文件,请检查文件格式或内容是否正确。")