| import gradio as gr |
| import pandas as pd |
| from datetime import datetime, timedelta |
| from reportlab.lib.pagesizes import letter, A4 |
| from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image |
| from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle |
| from reportlab.lib.units import inch |
| from reportlab.lib import colors |
| from reportlab.lib.enums import TA_CENTER, TA_LEFT |
| import tempfile |
| import os |
| from PIL import Image as PILImage |
|
|
| def generate_schedule(start_date, end_date, off_days, num_lessons, logo_file=None): |
| """ |
| Generate study schedule based on user inputs |
| """ |
| try: |
| |
| start = datetime.strptime(start_date, "%Y-%m-%d") |
| end = datetime.strptime(end_date, "%Y-%m-%d") |
| |
| |
| if start >= end: |
| return "β Error: Start date must be before end date!", None, None |
| |
| |
| day_mapping = { |
| "Monday": 0, "Tuesday": 1, "Wednesday": 2, "Thursday": 3, |
| "Friday": 4, "Saturday": 5, "Sunday": 6 |
| } |
| off_weekdays = [day_mapping[day] for day in off_days] |
| |
| |
| valid_dates = [] |
| current_date = start |
| |
| while current_date <= end: |
| if current_date.weekday() not in off_weekdays: |
| valid_dates.append(current_date) |
| current_date += timedelta(days=1) |
| |
| if not valid_dates: |
| return "β Error: No valid study days found in the selected range!", None, None |
| |
| if len(valid_dates) < num_lessons: |
| return f"β Error: Not enough study days ({len(valid_dates)}) for {num_lessons} lessons. Please extend your date range or reduce off-days.", None, None |
| |
| |
| schedule_data = [] |
| lessons_per_day = [1] * len(valid_dates) |
| |
| |
| if len(valid_dates) < num_lessons: |
| extra_lessons = num_lessons - len(valid_dates) |
| for i in range(extra_lessons): |
| lessons_per_day[i % len(valid_dates)] += 1 |
| |
| |
| lesson_counter = 1 |
| for i, date in enumerate(valid_dates): |
| if lesson_counter > num_lessons: |
| break |
| |
| for _ in range(lessons_per_day[i]): |
| if lesson_counter <= num_lessons: |
| schedule_data.append({ |
| "Date": date.strftime("%Y-%m-%d"), |
| "Day": date.strftime("%A"), |
| "Session": f"Session {lesson_counter}" |
| }) |
| lesson_counter += 1 |
| |
| |
| df = pd.DataFrame(schedule_data) |
| |
| |
| total_days = len(valid_dates) |
| total_weeks = (end - start).days // 7 + 1 |
| summary = f""" |
| π **Schedule Summary:** |
| β’ Total Lessons: {num_lessons} |
| β’ Study Period: {start_date} to {end_date} |
| β’ Available Study Days: {total_days} |
| β’ Estimated Duration: ~{total_weeks} weeks |
| β’ Off Days: {', '.join(off_days) if off_days else 'None'} |
| """ |
| |
| return summary, df, schedule_data |
| |
| except ValueError as e: |
| return f"β Error: Invalid date format. Please use YYYY-MM-DD format.", None, None |
| except Exception as e: |
| return f"β Error: {str(e)}", None, None |
|
|
| def create_pdf(schedule_data, start_date, end_date, off_days, num_lessons, logo_file=None): |
| """ |
| Create PDF from schedule data and return file path |
| """ |
| try: |
| |
| pdf_file = tempfile.NamedTemporaryFile(delete=False, suffix='.pdf', prefix='study_schedule_') |
| pdf_path = pdf_file.name |
| pdf_file.close() |
| |
| |
| doc = SimpleDocTemplate(pdf_path, pagesize=A4, rightMargin=72, leftMargin=72, |
| topMargin=72, bottomMargin=72) |
| |
| |
| elements = [] |
| |
| |
| styles = getSampleStyleSheet() |
| title_style = ParagraphStyle( |
| 'CustomTitle', |
| parent=styles['Heading1'], |
| fontSize=24, |
| spaceAfter=30, |
| alignment=TA_CENTER, |
| textColor=colors.HexColor('#2E86AB') |
| ) |
| |
| subtitle_style = ParagraphStyle( |
| 'CustomSubtitle', |
| parent=styles['Normal'], |
| fontSize=12, |
| spaceAfter=20, |
| alignment=TA_CENTER, |
| textColor=colors.HexColor('#666666') |
| ) |
| |
| |
| if logo_file is not None: |
| try: |
| |
| logo_temp = tempfile.NamedTemporaryFile(delete=False, suffix='.png') |
| logo_temp.write(logo_file) |
| logo_temp.close() |
| |
| |
| logo = Image(logo_temp.name, width=2*inch, height=1*inch) |
| logo.hAlign = 'CENTER' |
| elements.append(logo) |
| elements.append(Spacer(1, 20)) |
| |
| |
| os.unlink(logo_temp.name) |
| except Exception as e: |
| print(f"Logo processing error: {e}") |
| pass |
| |
| |
| title = Paragraph("π Study Schedule", title_style) |
| elements.append(title) |
| |
| |
| subtitle_text = f""" |
| Study Period: {start_date} to {end_date}<br/> |
| Total Lessons: {num_lessons}<br/> |
| Off Days: {', '.join(off_days) if off_days else 'None'} |
| """ |
| subtitle = Paragraph(subtitle_text, subtitle_style) |
| elements.append(subtitle) |
| elements.append(Spacer(1, 30)) |
| |
| |
| table_data = [['Date', 'Day', 'Session']] |
| for item in schedule_data: |
| table_data.append([item['Date'], item['Day'], item['Session']]) |
| |
| |
| table = Table(table_data, colWidths=[2*inch, 1.5*inch, 2*inch]) |
| |
| |
| table.setStyle(TableStyle([ |
| |
| ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#2E86AB')), |
| ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), |
| ('ALIGN', (0, 0), (-1, -1), 'CENTER'), |
| ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), |
| ('FONTSIZE', (0, 0), (-1, 0), 12), |
| |
| |
| ('BACKGROUND', (0, 1), (-1, -1), colors.beige), |
| ('TEXTCOLOR', (0, 1), (-1, -1), colors.black), |
| ('FONTNAME', (0, 1), (-1, -1), 'Helvetica'), |
| ('FONTSIZE', (0, 1), (-1, -1), 10), |
| |
| |
| ('GRID', (0, 0), (-1, -1), 1, colors.black), |
| ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), |
| |
| |
| ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#F5F5F5')]) |
| ])) |
| |
| elements.append(table) |
| |
| |
| elements.append(Spacer(1, 30)) |
| footer = Paragraph(f"Generated on {datetime.now().strftime('%Y-%m-%d %H:%M')}", |
| styles['Normal']) |
| elements.append(footer) |
| |
| |
| doc.build(elements) |
| |
| return pdf_path |
| |
| except Exception as e: |
| print(f"PDF creation error: {str(e)}") |
| return None |
|
|
| def process_schedule(start_date, end_date, off_days, num_lessons, logo_file): |
| """ |
| Main processing function for Gradio interface |
| """ |
| try: |
| |
| summary, df, schedule_data = generate_schedule(start_date, end_date, off_days, num_lessons, logo_file) |
| |
| if schedule_data is None: |
| return summary, None, None, gr.update(visible=False) |
| |
| |
| pdf_path = create_pdf(schedule_data, start_date, end_date, off_days, num_lessons, logo_file) |
| |
| if pdf_path is None: |
| return summary, df, None, gr.update(visible=False) |
| |
| return summary, df, pdf_path, gr.update(visible=True) |
| |
| except Exception as e: |
| error_msg = f"β Error processing schedule: {str(e)}" |
| return error_msg, None, None, gr.update(visible=False) |
|
|
| |
| def create_interface(): |
| with gr.Blocks( |
| title="π Study Schedule Generator", |
| theme=gr.themes.Soft(), |
| css=""" |
| .main-header { |
| text-align: center; |
| color: #2E86AB; |
| margin-bottom: 2rem; |
| } |
| .info-box { |
| background: #f0f9ff; |
| padding: 1rem; |
| border-radius: 8px; |
| border-left: 4px solid #2E86AB; |
| margin: 1rem 0; |
| } |
| .generate-btn { |
| background: linear-gradient(45deg, #2E86AB, #A23B72) !important; |
| border: none !important; |
| color: white !important; |
| font-weight: bold !important; |
| font-size: 16px !important; |
| padding: 12px 24px !important; |
| border-radius: 8px !important; |
| } |
| """ |
| ) as demo: |
| |
| gr.Markdown( |
| """ |
| # π Study Schedule Generator |
| |
| <div class="info-box"> |
| <strong>How it works:</strong><br> |
| 1. Set your study period (start and end dates)<br> |
| 2. Choose your off-days (days you don't want to study)<br> |
| 3. Set the number of lessons to schedule<br> |
| 4. Optionally upload a logo for your PDF<br> |
| 5. Generate and download your personalized study schedule as PDF! |
| </div> |
| """, |
| elem_classes=["main-header"] |
| ) |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| gr.Markdown("### π
Schedule Settings") |
| |
| start_date = gr.Textbox( |
| label="π
Start Date (YYYY-MM-DD format)", |
| placeholder="YYYY-MM-DD (e.g., 2025-07-01)", |
| value="2025-07-01" |
| ) |
| |
| end_date = gr.Textbox( |
| label="π
End Date (YYYY-MM-DD format)", |
| placeholder="YYYY-MM-DD (e.g., 2025-08-30)", |
| value="2025-08-30" |
| ) |
| |
| num_lessons = gr.Slider( |
| minimum=1, |
| maximum=200, |
| value=90, |
| step=1, |
| label="π Number of Lessons" |
| ) |
| |
| off_days = gr.CheckboxGroup( |
| choices=["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], |
| label="π« Off Days (Select days you don't want to study)", |
| value=["Sunday"] |
| ) |
| |
| logo_file = gr.File( |
| label="πΌοΈ Upload Logo (Optional - PNG, JPG supported)", |
| file_types=["image"], |
| type="binary" |
| ) |
| |
| generate_btn = gr.Button( |
| "π Generate Schedule", |
| variant="primary", |
| size="lg", |
| elem_classes=["generate-btn"] |
| ) |
| |
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("### π Results") |
| |
| summary_output = gr.Markdown() |
| |
| schedule_table = gr.Dataframe( |
| headers=["Date", "Day", "Session"], |
| interactive=False, |
| wrap=True |
| ) |
| |
| pdf_download = gr.File( |
| label="π Download PDF Schedule", |
| visible=False, |
| interactive=True |
| ) |
| |
| |
| generate_btn.click( |
| fn=process_schedule, |
| inputs=[start_date, end_date, off_days, num_lessons, logo_file], |
| outputs=[summary_output, schedule_table, pdf_download, pdf_download] |
| ) |
| |
| |
| gr.Markdown( |
| """ |
| ### π‘ Usage Tips: |
| |
| **π
Date Format**: Always use YYYY-MM-DD format |
| - β
Correct: `2025-07-01` |
| - β Wrong: `07/01/2025` or `1 July 2025` |
| |
| **π« Off Days**: Select any days you want to skip |
| - Example: Skip weekends β Select "Saturday" and "Sunday" |
| - Example: Skip Friday prayers β Select "Friday" |
| |
| **π Lessons**: Adjust based on your course |
| - Online course with 90 videos β Set to 90 |
| - Textbook with 20 chapters β Set to 20 |
| |
| **πΌοΈ Logo**: Upload any image to brand your PDF |
| - Supported: PNG, JPG, JPEG, GIF |
| - Recommended size: 200x100 pixels or similar ratio |
| |
| ### π― Example Scenarios: |
| |
| **Scenario 1: Weekend Study** |
| - Start: 2025-07-01, End: 2025-12-31 |
| - Off Days: Monday, Tuesday, Wednesday, Thursday, Friday |
| - Lessons: 50 |
| - Result: Weekend-only schedule |
| |
| **Scenario 2: Intensive Course** |
| - Start: 2025-07-01, End: 2025-07-31 |
| - Off Days: Sunday |
| - Lessons: 90 |
| - Result: ~3-4 lessons per day |
| |
| **Scenario 3: Relaxed Learning** |
| - Start: 2025-07-01, End: 2025-12-31 |
| - Off Days: Friday, Saturday, Sunday |
| - Lessons: 100 |
| - Result: ~1 lesson per weekday |
| """ |
| ) |
| |
| return demo |
|
|
| if __name__ == "__main__": |
| |
| demo = create_interface() |
| demo.launch( |
| server_name="0.0.0.0", |
| server_port=7860, |
| share=False, |
| debug=True |
| ) |