# ============================================================================== # 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...\n<|user|>\nTemat kursu: \"{topic}\"\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("
Twoje centrum dowodzenia do tworzenia multimedialnych kursów DIY
") images_state = gr.State([]) with gr.Row(equal_height=True): with gr.Column(scale=2): gr.Markdown("