Spaces:
Runtime error
Runtime error
Download app.py from HardbanRecordsLab/diy-course-app: direct link, hf CLI and curl.
- Browser
- Download file 21.4 kB
-
https://huggingface.co/spaces/HardbanRecordsLab/diy-course-app/resolve/main/app.py
- Command line
-
hf download hf://spaces/HardbanRecordsLab/diy-course-app/app.py
-
curl -L -o app.py https://huggingface.co/spaces/HardbanRecordsLab/diy-course-app/resolve/main/app.py
21.4 kB
| # ============================================================================== | |
| # Wymuszona instalacja bibliotek | |
| # ============================================================================== | |
| import os | |
| import subprocess | |
| import sys | |
| def install_packages(): | |
| packages = [ | |
| "gradio>=4.0.0", "transformers>=4.38.1", "torch>=2.2.0", | |
| "accelerate>=0.27.2", "diffusers>=0.26.3", "fpdf2", "Pillow", | |
| "datasets", "gradio_client", "speechbrain", "soundfile", "moviepy==1.0.3" | |
| ] | |
| for package in packages: | |
| try: | |
| module_name = package.split('>')[0].split('=')[0].split('<')[0] | |
| __import__(module_name) | |
| print(f"{package} jest już zainstalowany.") | |
| except ImportError: | |
| print(f"Instalowanie {package}...") | |
| subprocess.check_call([sys.executable, "-m", "pip", "install", package]) | |
| if 'PACKAGES_INSTALLED' not in os.environ: | |
| install_packages() | |
| os.environ['PACKAGES_INSTALLED'] = 'TRUE' | |
| # ============================================================================== | |
| # Główny kod aplikacji | |
| # ============================================================================== | |
| import gradio as gr | |
| from transformers import pipeline as text_pipeline | |
| from diffusers import DiffusionPipeline | |
| import torch | |
| import re | |
| from fpdf import FPDF | |
| from PIL import Image | |
| import time | |
| from datasets import Dataset, Features, Value, Sequence, Image as HFImage | |
| from gradio_client import Client | |
| from transformers import SpeechT5Processor, SpeechT5ForTextToSpeech, SpeechT5HifiGan | |
| import soundfile as sf | |
| import numpy as np | |
| from moviepy.editor import ImageClip, concatenate_videoclips, AudioFileClip, CompositeAudioClip | |
| from speechbrain.pretrained import EncoderClassifier | |
| # --- Konfiguracja --- | |
| LLM_MODEL = "HuggingFaceH4/zephyr-7b-beta" | |
| IMAGE_MODEL = "stabilityai/stable-diffusion-2-1-base" | |
| TTS_MODEL = "microsoft/speecht5_tts" | |
| VOCODER_MODEL = "microsoft/speecht5_hifigan" | |
| SPEAKER_EMBEDDING_MODEL = "speechbrain/spkrec-xvect-voxceleb" | |
| DATASET_PATH = "saved_courses" | |
| TEXT_TO_3D_MODEL_SPACE = "stabilityai/TripoSR" | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| # --- Globalne zmienne dla modeli --- | |
| text_generator, image_generator, client_3d, tts_processor, tts_model, vocoder, speaker_embeddings = (None,) * 7 | |
| LLM_LOADED, IMAGE_MODEL_LOADED, CLIENT_3D_LOADED, TTS_LOADED = (False,) * 4 | |
| # --- Funkcje ładowania modeli --- | |
| def load_all_models(): | |
| global text_generator, image_generator, client_3d, tts_processor, tts_model, vocoder, speaker_embeddings | |
| global LLM_LOADED, IMAGE_MODEL_LOADED, CLIENT_3D_LOADED, TTS_LOADED | |
| try: | |
| print("Ładowanie modelu językowego..."); text_generator = text_pipeline("text-generation", model=LLM_MODEL, torch_dtype=torch.bfloat16, device_map="auto"); LLM_LOADED = True; print("Model językowy załadowany.") | |
| except Exception as e: print(f"Błąd ładowania LLM: {e}") | |
| try: | |
| print("Ładowanie modelu obrazkowego..."); image_generator = DiffusionPipeline.from_pretrained(IMAGE_MODEL); image_generator.to(device); IMAGE_MODEL_LOADED = True; print("Model obrazkowy załadowany.") | |
| except Exception as e: print(f"Błąd ładowania Image Model: {e}") | |
| try: | |
| print("Inicjalizacja klienta 3D..."); client_3d = Client(TEXT_TO_3D_MODEL_SPACE); CLIENT_3D_LOADED = True; print("Klient 3D gotowy.") | |
| except Exception as e: print(f"Błąd inicjalizacji klienta 3D: {e}") | |
| try: | |
| print("Ładowanie modeli TTS...") | |
| tts_processor = SpeechT5Processor.from_pretrained(TTS_MODEL) | |
| tts_model = SpeechT5ForTextToSpeech.from_pretrained(TTS_MODEL).to(device) | |
| vocoder = SpeechT5HifiGan.from_pretrained(VOCODER_MODEL).to(device) | |
| speaker_model = EncoderClassifier.from_hparams(source=SPEAKER_EMBEDDING_MODEL, savedir=os.path.join('/tmp', SPEAKER_EMBEDDING_MODEL)) | |
| dummy_waveform = torch.randn(1, 16000).to(device) | |
| speaker_embeddings = speaker_model.encode_batch(dummy_waveform).squeeze().to(device) | |
| TTS_LOADED = True | |
| print("Modele TTS załadowane.") | |
| except Exception as e: print(f"Błąd ładowania modeli TTS: {e}") | |
| # Zwracamy informację do UI, że modele są gotowe | |
| return gr.update(interactive=True), gr.update(interactive=True) | |
| # --- Funkcje pomocnicze i główne --- | |
| def list_saved_projects(): | |
| if not os.path.exists(DATASET_PATH): return [] | |
| try: | |
| dataset = Dataset.load_from_disk(DATASET_PATH) | |
| return dataset["project_name"] | |
| except Exception as e: | |
| print(f"Błąd odczytu projektów: {e}"); return [] | |
| def parse_course_to_structure(markdown_text): | |
| course_data = {'title': "Nowy Kurs", 'steps': []} | |
| title_match = re.search(r"^#\s*(.*)", markdown_text, re.MULTILINE) | |
| if title_match: course_data['title'] = title_match.group(1).strip() | |
| step_pattern = re.compile(r"(\d+\.\s*\*\*(.*?)\*\*)\s*([\s\S]*?)(?=\n\d+\.|\Z)", re.MULTILINE) | |
| steps_found = step_pattern.findall(markdown_text) | |
| for _, step_title, step_description in steps_found: | |
| course_data['steps'].append({'title': step_title.strip(), 'description': step_description.strip()}) | |
| return course_data | |
| def generate_course_structure_and_images(topic, progress=gr.Progress(track_tqdm=True)): | |
| if not LLM_LOADED or not IMAGE_MODEL_LOADED: | |
| gr.Error("Kluczowe modele AI nie zostały załadowane. Sprawdź logi.") | |
| return [None, gr.State([])] + [gr.update(visible=False)]*2 + [gr.update(interactive=False)]*3 + [gr.update()]*11 | |
| progress(0, desc="Generowanie tekstu kursu...") | |
| prompt = f"<|system|>\nJesteś ekspertem w tworzeniu kursów online...</s>\n<|user|>\nTemat kursu: \"{topic}\"</s>\n<|assistant|>" | |
| response = text_generator(prompt, max_new_tokens=1024, do_sample=True, temperature=0.7, top_p=0.95) | |
| course_text = response[0]['generated_text'].split('<|assistant|>')[-1].strip() | |
| course_data = parse_course_to_structure(course_text) | |
| progress(0.2, desc="Generowanie obrazków...") | |
| generated_images = [] | |
| if course_data['steps']: | |
| for i, step in enumerate(course_data['steps']): | |
| progress(0.2 + (i + 1) / len(course_data['steps']) * 0.8, desc=f"Generowanie obrazka dla: {step['title']}") | |
| image_prompt = f"cinematic photo of '{step['title']}', professional photography, high detail, 8k" | |
| image = image_generator(image_prompt, num_inference_steps=25).images[0] | |
| generated_images.append(image) | |
| updates = [gr.update(value=course_data['title'])] | |
| for i in range(5): | |
| if i < len(course_data['steps']): | |
| updates.extend([gr.update(value=course_data['steps'][i]['title']), gr.update(value=course_data['steps'][i]['description'])]) | |
| else: | |
| updates.extend(["", ""]) | |
| return generated_images, gr.State(generated_images), gr.update(visible=True), gr.update(visible=True), gr.update(interactive=True), gr.update(interactive=True), gr.update(interactive=True), *updates | |
| def load_project(project_name): | |
| if not project_name: | |
| gr.Warning("Wybierz projekt do wczytania.") | |
| return [None, gr.State([])] + [gr.update(visible=False)]*2 + [gr.update(interactive=False)]*3 + [gr.update()]*11 | |
| dataset = Dataset.load_from_disk(DATASET_PATH) | |
| project_data = dataset.filter(lambda example: example["project_name"] == project_name)[0] | |
| loaded_images = project_data["images"] | |
| updates = [gr.update(value=project_data['course_title'])] | |
| for i in range(5): | |
| if i < len(project_data['steps']): | |
| step = project_data['steps'][i] | |
| updates.extend([gr.update(value=step['title']), gr.update(value=step['description'])]) | |
| else: | |
| updates.extend(["", ""]) | |
| return loaded_images, gr.State(loaded_images), gr.update(visible=True), gr.update(visible=True), gr.update(interactive=True), gr.update(interactive=True), gr.update(interactive=True), *updates | |
| def update_preview(course_title, *args): | |
| markdown_preview = f"# {course_title}\n\n" | |
| step_data = list(args) | |
| for i in range(0, len(step_data), 2): | |
| step_title, step_description = step_data[i], step_data[i+1] | |
| if step_title: | |
| markdown_preview += f"### Krok {i//2 + 1}: {step_title}\n{step_description}\n\n" | |
| return markdown_preview | |
| def move_step(direction, step_index, images_state, *text_fields): | |
| if direction == "up": | |
| if step_index == 0: return [images_state] + [gr.update() for _ in text_fields] | |
| swap_with_index = step_index - 1 | |
| else: | |
| if step_index >= 4 or not text_fields[(step_index + 1) * 2]: return [images_state] + [gr.update() for _ in text_fields] | |
| swap_with_index = step_index + 1 | |
| images_state[step_index], images_state[swap_with_index] = images_state[swap_with_index], images_state[step_index] | |
| current_title, current_desc = text_fields[step_index * 2], text_fields[step_index * 2 + 1] | |
| swap_title, swap_desc = text_fields[swap_with_index * 2], text_fields[swap_with_index * 2 + 1] | |
| updates = list(text_fields) | |
| updates[step_index * 2], updates[step_index * 2 + 1] = swap_title, swap_desc | |
| updates[swap_with_index * 2], updates[swap_with_index * 2 + 1] = current_title, current_desc | |
| final_updates = [gr.update(value=v) for v in updates] | |
| return [images_state] + final_updates | |
| def generate_3d_model(prompt_text, progress=gr.Progress(track_tqdm=True)): | |
| if not CLIENT_3D_LOADED: | |
| gr.Warning("Klient do generowania modeli 3D nie jest dostępny.") | |
| return None | |
| if not prompt_text: | |
| gr.Info("Wpisz tytuł kroku, aby wygenerować model 3D.") | |
| return None | |
| progress(0, desc="Wysyłanie zapytania do modelu 3D... (to może potrwać kilka minut)") | |
| result_path = client_3d.predict(prompt_text, None, True, 0.5, 1024, 256, api_name="/generate") | |
| progress(1, desc="Model 3D gotowy!") | |
| return result_path | |
| def generate_audio(text, progress=gr.Progress(track_tqdm=True)): | |
| if not TTS_LOADED: | |
| gr.Warning("Modele Text-to-Speech nie są dostępne.") | |
| return None | |
| if not text: | |
| gr.Info("Brak tekstu do wygenerowania audio.") | |
| return None | |
| progress(0, desc="Generowanie audio...") | |
| inputs = tts_processor(text=text, return_tensors="pt") | |
| speech = tts_model.generate_speech(inputs["input_ids"].to(device), speaker_embeddings.unsqueeze(0), vocoder=vocoder) | |
| output_path = f"/tmp/audio_{int(time.time())}.wav" | |
| sf.write(output_path, speech.cpu().numpy(), samplerate=16000) | |
| progress(1, desc="Audio gotowe!") | |
| return output_path | |
| def generate_video_from_ui(images_state, course_title, *args, progress=gr.Progress(track_tqdm=True)): | |
| if not images_state or not course_title: | |
| gr.Warning("Najpierw wygeneruj kurs, aby stworzyć wideo.") | |
| return None | |
| step_data = list(args) | |
| audio_clips, video_clips, temp_audio_files = [], [], [] | |
| total_steps = len(images_state) | |
| for i in range(total_steps): | |
| progress((i + 1) / (total_steps + 1), desc=f"Tworzenie audio dla kroku {i+1}/{total_steps}") | |
| step_description = step_data[i*2+1] | |
| if not step_description: continue | |
| audio_path = generate_audio(step_description) | |
| if audio_path: | |
| audio_clip = AudioFileClip(audio_path) | |
| audio_clips.append(audio_clip) | |
| temp_audio_files.append(audio_path) | |
| video_clip = ImageClip(np.array(images_state[i])).set_duration(audio_clip.duration) | |
| video_clips.append(video_clip) | |
| if not video_clips: | |
| gr.Warning("Nie udało się stworzyć żadnych klipów wideo.") | |
| return None | |
| progress(0.9, desc="Montowanie finalnego wideo...") | |
| final_video = concatenate_videoclips(video_clips, method="compose") | |
| final_video.audio = CompositeAudioClip(audio_clips) | |
| video_output_path = f"/tmp/final_video_{int(time.time())}.mp4" | |
| final_video.write_videofile(video_output_path, fps=24, codec="libx24") | |
| for f in temp_audio_files: os.remove(f) | |
| progress(1, desc="Wideo gotowe!") | |
| return video_output_path | |
| def save_project(project_name, images_state, course_title, *args): | |
| if not project_name: return "⚠️ Proszę podać nazwę projektu." | |
| steps_list = [] | |
| step_data = list(args) | |
| for i in range(0, len(step_data), 2): | |
| if step_data[i]: steps_list.append({'title': step_data[i], 'description': step_data[i+1]}) | |
| new_item = {'project_name': project_name, 'course_title': course_title, 'steps': steps_list, 'images': images_state} | |
| features = Features({'project_name': Value('string'), 'course_title': Value('string'), 'steps': Sequence({'title': Value('string'), 'description': Value('string')}), 'images': Sequence(HFImage())}) | |
| if os.path.exists(DATASET_PATH): | |
| dataset = Dataset.load_from_disk(DATASET_PATH) | |
| else: | |
| dataset = Dataset.from_dict({key: [] for key in features.keys()}, features=features) | |
| dataset = dataset.add_item(new_item) | |
| dataset.save_to_disk(DATASET_PATH) | |
| return f"✅ Projekt '{project_name}' został pomyślnie zapisany!" | |
| def generate_pdf_from_ui(images_state, course_title, *args): | |
| if not course_title or not images_state: return None | |
| pdf = FPDF() | |
| pdf.add_page() | |
| try: | |
| pdf.add_font('DejaVu', '', 'DejaVuSans.ttf', uni=True); pdf.set_font('DejaVu', '', 12) | |
| except RuntimeError: | |
| print("Brak czcionki DejaVu, używam 'Arial'."); pdf.set_font('Arial', '', 12) | |
| pdf.set_font_size(24) | |
| pdf.multi_cell(0, 10, course_title.encode('latin-1', 'replace').decode('latin-1')); pdf.ln(10) | |
| temp_image_paths = [] | |
| for i, img in enumerate(images_state): | |
| path = f"/tmp/temp_image_{i}.png"; img.save(path); temp_image_paths.append(path) | |
| step_data = list(args); image_counter = 0 | |
| for i in range(0, len(step_data), 2): | |
| step_title, step_description = step_data[i], step_data[i+1] | |
| if step_title: | |
| pdf.set_font_size(16) | |
| pdf.multi_cell(0, 8, f"Krok {i//2 + 1}: {step_title}".encode('latin-1', 'replace').decode('latin-1')); pdf.ln(4) | |
| if image_counter < len(temp_image_paths): | |
| pdf.image(temp_image_paths[image_counter], x=None, y=None, w=180); pdf.ln(5); image_counter += 1 | |
| pdf.set_font_size(12) | |
| pdf.multi_cell(0, 6, step_description.encode('latin-1', 'replace').decode('latin-1')); pdf.ln(10) | |
| pdf_output_path = f"/tmp/kurs_{int(time.time())}.pdf"; pdf.output(pdf_output_path) | |
| for path in temp_image_paths: os.remove(path) | |
| return pdf_output_path | |
| # --- Budowa Interfejsu Gradio --- | |
| custom_css = "body, #root { height: 100vh; margin: 0; padding: 0; } .gradio-container { max-width: 100% !important; } .main, .wrap, .gap { height: 100%; } .app-title { text-align: center; font-size: 2.5em; color: #2c3e50; font-weight: bold; margin-bottom: 0px !important; } .app-subtitle { text-align: center; font-size: 1.1em; color: #576574; margin-top: 5px; margin-bottom: 20px; } .section-title { font-size: 1.5em; font-weight: bold; color: #2980b9; border-bottom: 2px solid #3498db; padding-bottom: 5px; margin-top: 10px; } footer { display: none !important; }" | |
| with gr.Blocks(theme=gr.themes.Soft(primary_hue="sky", secondary_hue="blue"), title="Kreator Kursów AI", css=custom_css) as demo: | |
| gr.HTML("<h1 class='app-title'>🎨 Kreator Kursów AI 🚀</h1>") | |
| gr.HTML("<p class='app-subtitle'>Twoje centrum dowodzenia do tworzenia multimedialnych kursów DIY</p>") | |
| images_state = gr.State([]) | |
| with gr.Row(equal_height=True): | |
| with gr.Column(scale=2): | |
| gr.Markdown("<h2 class='section-title'>⚙️ Panel Sterowania</h2>") | |
| with gr.Group(): | |
| gr.Markdown("### 1. Wygeneruj lub Wczytaj Projekt") | |
| topic_input = gr.Textbox(label="Wpisz temat nowego kursu", placeholder="np. Jak zbudować domek dla jeży?", lines=3, interactive=False) | |
| generate_button = gr.Button("🚀 Wygeneruj Nowy Kurs!", variant="primary", interactive=False) | |
| with gr.Accordion("📚 Biblioteka Projektów", open=True): | |
| saved_projects_dropdown = gr.Dropdown(label="Wybierz zapisany projekt", choices=list_saved_projects(), interactive=True) | |
| load_project_button = gr.Button("📂 Wczytaj Projekt") | |
| with gr.Group(): | |
| gr.Markdown("### 3. Zapisz Postępy") | |
| project_name_input = gr.Textbox(label="Nazwa Projektu", placeholder="Mój kurs o jeżach...") | |
| save_button = gr.Button("💾 Zapisz Projekt", interactive=False) | |
| save_confirmation_output = gr.Markdown() | |
| with gr.Column(scale=4): | |
| gr.Markdown("<h2 class='section-title'>✍️ Główny Edytor</h2>") | |
| with gr.Group(visible=False) as editor_group: | |
| course_title_editor = gr.Textbox(label="Tytuł Główny Kursu", interactive=True) | |
| step_editors = [] | |
| step_text_editors = [] | |
| for i in range(5): | |
| with gr.Accordion(f"Krok {i+1}", open=True): | |
| with gr.Row(): | |
| with gr.Column(scale=10): | |
| step_title = gr.Textbox(label="Tytuł kroku", interactive=True) | |
| step_description = gr.Textbox(label="Opis kroku", lines=5, interactive=True) | |
| with gr.Column(scale=1, min_width=80): | |
| up_button = gr.Button("🔼") | |
| down_button = gr.Button("🔽") | |
| with gr.Row(): | |
| generate_3d_btn = gr.Button("🧊 3D") | |
| generate_audio_btn = gr.Button("🎵 Audio") | |
| step_editors.extend([step_title, step_description, generate_3d_btn, generate_audio_btn, up_button, down_button]) | |
| step_text_editors.extend([step_title, step_description]) | |
| with gr.Column(scale=3): | |
| gr.Markdown("<h2 class='section-title'>👁️ Podgląd i Eksport</h2>") | |
| with gr.Group(visible=False) as preview_group: | |
| with gr.Tabs(): | |
| with gr.TabItem("Tekst"): | |
| preview_display = gr.Markdown("Podgląd kursu pojawi się tutaj...") | |
| with gr.TabItem("Multimedia"): | |
| image_gallery = gr.Gallery(label="Ilustracje", show_label=False, columns=3, height="auto") | |
| model_3d_display = gr.Model3D(label="Model 3D", show_label=False) | |
| audio_player = gr.Audio(label="Lektor", show_label=False) | |
| gr.Markdown("### 4. Pobierz Gotowy Kurs") | |
| with gr.Row(): | |
| pdf_button = gr.Button("📄 PDF", interactive=False) | |
| video_button = gr.Button("🎬 Wideo", interactive=False) | |
| file_output = gr.File(label="Pobierz PDF") | |
| video_output = gr.Video(label="Pobierz Wideo", format="mp4") | |
| # --- Logika Akcji --- | |
| editor_text_fields = [course_title_editor] + step_text_editors | |
| all_outputs_for_load_and_generate = [ | |
| image_gallery, images_state, editor_group, preview_group, | |
| pdf_button, save_button, video_button | |
| ] + editor_text_fields | |
| demo.load( | |
| fn=load_all_models, | |
| inputs=None, | |
| outputs=[generate_button, topic_input] | |
| ) | |
| generate_button.click( | |
| fn=generate_course_structure_and_images, | |
| inputs=[topic_input], | |
| outputs=all_outputs_for_load_and_generate | |
| ).then(fn=update_preview, inputs=editor_text_fields, outputs=preview_display) | |
| load_project_button.click( | |
| fn=load_project, | |
| inputs=[saved_projects_dropdown], | |
| outputs=all_outputs_for_load_and_generate | |
| ).then(fn=update_preview, inputs=editor_text_fields, outputs=preview_display) | |
| for editor_component in editor_text_fields: | |
| editor_component.change(fn=update_preview, inputs=editor_text_fields, outputs=preview_display) | |
| pdf_button.click(fn=generate_pdf_from_ui, inputs=[images_state] + editor_text_fields, outputs=file_output) | |
| save_button.click(fn=save_project, inputs=[project_name_input, images_state] + editor_text_fields, outputs=[save_confirmation_output]) | |
| for i in range(5): | |
| step_title_comp, step_desc_comp, btn_3d_comp, btn_audio_comp, up_btn, down_btn = step_editors[i*6 : (i+1)*6] | |
| btn_3d_comp.click(fn=generate_3d_model, inputs=[step_title_comp], outputs=[model_3d_display]) | |
| btn_audio_comp.click(fn=generate_audio, inputs=[step_desc_comp], outputs=[audio_player]) | |
| up_btn.click( | |
| fn=move_step, | |
| inputs=[gr.State("up"), gr.State(i), images_state] + step_text_editors, | |
| outputs=[images_state] + step_text_editors | |
| ).then(fn=update_preview, inputs=editor_text_fields, outputs=preview_display) | |
| down_btn.click( | |
| fn=move_step, | |
| inputs=[gr.State("down"), gr.State(i), images_state] + step_text_editors, | |
| outputs=[images_state] + step_text_editors | |
| ).then(fn=update_preview, inputs=editor_text_fields, outputs=preview_display) | |
| video_button.click( | |
| fn=generate_video_from_ui, | |
| inputs=[images_state] + editor_text_fields, | |
| outputs=[video_output] | |
| ) | |
| demo.queue().launch(show_api=False) | |