# YtQGen: YouTube to Questionnaire Generator

YtQGen is a Python library and CLI that turns educational YouTube videos into clean, printable questionnaires. It downloads audio, transcribes speech, summarizes content, and generates balanced questions, then exports a PDF.

## Highlights

- Multiple AI providers: OpenAI, Mistral, and Groq for both LLM tasks and transcription
- No external ffmpeg required: audio handled via PyAV and yt-dlp
- Robust on long inputs: automatic audio chunking and summary chunking
- Configurable outputs: control question count, type mix, difficulty, and style
- Plain-text safe for PDFs: prompts enforce ASCII-safe formatting for reliable PDF rendering
- CLI and Python API, with YAML config and .env support

## Requirements

- Python 3.10 or newer
- API key for at least one provider
  - OpenAI: https://platform.openai.com/api-keys
  - Mistral: https://console.mistral.ai/api-keys/
  - Groq: https://console.groq.com/keys

## Installation

Install from PyPI using extras for your provider:

```bash
# Core package only
pip install ytqgen

# With a specific provider
pip install "ytqgen[openai]"
pip install "ytqgen[mistral]"
pip install "ytqgen[groq]"

# With all providers
pip install "ytqgen[all]"

# For development and tests
pip install -e ".[dev]"
```

The package entry point installs a command named `ytqgen`.

## Quickstart

### CLI

```bash
# Minimal: provide a YouTube URL and choose an LLM provider
ytqgen process --input "https://www.youtube.com/watch?v=VIDEO_ID" --provider openai --output quiz.pdf

# With a config file that includes keys and defaults
ytqgen process --config examples/config.yml --input "https://www.youtube.com/watch?v=VIDEO_ID"

# Override generation settings at the CLI
ytqgen process \
  --input "https://www.youtube.com/watch?v=VIDEO_ID" \
  --num-questions 20 \
  --mcq-percentage 40 \
  --subjective-percentage 60
```

CLI precedence: CLI arguments override YAML config, which overrides .env, which overrides existing environment variables.

Notes
- The CLI currently accepts only YouTube URLs for `--input`.
- YouTube downloads are best run on a local machine. Some cloud or hosted environments may block downloads.

### Python API

```python
import os
from ytqgen import process_stream

# Configure providers and keys via environment
os.environ["LLM_PROVIDER"] = "openai"  # or "mistral" or "groq"
os.environ["TRANSCRIPTION_PROVIDER"] = "openai"  # or "mistral" or "groq"
os.environ["OPENAI_API_KEY"] = "your-openai-key"

pdf_path = process_stream("https://www.youtube.com/watch?v=VIDEO_ID", output_pdf="questionnaire.pdf")
print(pdf_path)
```

## Configuration

You can configure YtQGen with a YAML file, environment variables, or CLI arguments. A sample is provided in `examples/config_template.yml`.

Supported keys in YAML

- provider: LLM provider for summarization and question generation. One of openai, mistral, groq
- transcription_provider: provider for audio transcription. One of openai, mistral, groq
- keys: API keys per provider
  - openai: string
  - mistral: string
  - groq: string
- models: optional override of model names per provider
  - openai: for example gpt-4o-mini, gpt-4o, gpt-4, gpt-3.5-turbo
  - mistral: for example mistral-small-latest, mistral-medium-latest, mistral-large-latest
  - groq: for example llama-3.1-70b-versatile, llama-3.3-70b-versatile, mixtral-8x7b-32768
- generation: questionnaire settings
  - num_questions: integer greater than 0
  - question_types: percentages that sum to 100
    - mcq_percentage
    - subjective_percentage
  - difficulty: percentages that sum to 100
    - easy_percentage
    - medium_percentage
    - hard_percentage
  - style: percentages that sum to 100 (applied to subjective)
    - scenario_percentage
    - theoretical_percentage

Example

```yaml
input: https://www.youtube.com/watch?v=dQw4w9WgXcQ
output: my_questionnaire.pdf

provider: openai
transcription_provider: openai

keys:
  openai: YOUR_OPENAI_API_KEY

models:
  openai: gpt-4o-mini

generation:
  num_questions: 10
  question_types:
    mcq_percentage: 20
    subjective_percentage: 80
  difficulty:
    easy_percentage: 30
    medium_percentage: 50
    hard_percentage: 20
  style:
    scenario_percentage: 40
    theoretical_percentage: 60
```

Environment variables

- LLM_PROVIDER, TRANSCRIPTION_PROVIDER
- OPENAI_API_KEY, MISTRAL_API_KEY, GROQ_API_KEY
- YTQGEN_OPENAI_MODEL, YTQGEN_MISTRAL_MODEL, YTQGEN_GROQ_MODEL
- YTQGEN_NUM_QUESTIONS, YTQGEN_MCQ_PERCENTAGE, YTQGEN_SUBJECTIVE_PERCENTAGE
- YTQGEN_EASY_PERCENTAGE, YTQGEN_MEDIUM_PERCENTAGE, YTQGEN_HARD_PERCENTAGE
- YTQGEN_SCENARIO_PERCENTAGE, YTQGEN_THEORETICAL_PERCENTAGE

## Providers and models

Transcription
- OpenAI: Whisper model whisper-1
- Groq: Whisper model whisper-large-v3
- Mistral: Voxtral endpoint voxtral-mini-latest

LLM summarization and question generation
- OpenAI: defaults to gpt-4o-mini unless overridden
- Mistral: defaults to mistral-small-latest unless overridden
- Groq: set with YTQGEN_GROQ_MODEL. Common choices include llama-3.1-70b-versatile, openai/gpt-oss-120b, and openai/gpt-oss-20b

Set explicit model names in your YAML or environment when in doubt.

If you are lost regarding models, check out the respective documentations.
- [OpenAI Documentation](https://api.openai.com/v1/models)
- [Groq Documentation](https://console.groq.com/docs/models)
- [Mistral Documentation](https://docs.mistral.ai/getting-started/models)

## How it works

Pipeline steps
1. Download audio with yt-dlp and convert to MP3 with PyAV
2. Transcribe audio with the selected provider. Long audio is automatically split into 10 minute chunks
3. Summarize the transcription using the selected LLM. Long summaries are created via chunked summarization and a final synthesis pass
4. Generate a balanced questionnaire from the summary using configurable distributions
5. Render a plain text PDF with PyMuPDF for broad compatibility

Key modules
- `ytqgen.audio_processor`: audio download and conversion with PyAV
- `ytqgen.transcription`: transcription with OpenAI, Groq, or Mistral, with chunking
- `ytqgen.summarizer`: chunked summarization using provider LLMs
- `ytqgen.questionnaire_generator`: dynamic prompts and distribution controls
- `ytqgen.pdf_generator`: plain text PDF writer using PyMuPDF
- `ytqgen.cli`: CLI implementation with YAML config and validation
- `ytqgen.pipeline`: orchestrates the end to end flow

## Examples

- `examples/config_template.yml`: copy to your project and fill in keys
- `examples/example_youtube.py`: simple programmatic usage with a YouTube URL

Note: A sample `examples/example_local_file.py` exists in the repository but the current pipeline expects a YouTube URL. Local file processing is not yet supported by the default pipeline.

## Troubleshooting

Common issues

- Missing API key: set the corresponding environment variable or place it in YAML under `keys`
- Invalid input: the CLI accepts only YouTube URLs
- yt-dlp blocked: some hosted environments block downloads. Run locally or provide a different network
- Audio conversion errors: ensure the `av` package is installed and up to date
- PDF output looks odd: the generator enforces ASCII in prompts, but if your model returns formatted text, reduce creativity or re-run

Update tools

```bash
pip install --upgrade yt-dlp av PyMuPDF
```

## Development and testing

Install dev dependencies and run the test suite

```bash
pip install -e ".[dev]"
pytest tests/ -v
```

Useful targets
- Run import checks: `python test_imports.py`
- Try a fast configuration: `ytqgen process --input "https://youtube.com/watch?v=VIDEO_ID" --config test_config.yml --output test_output.pdf`

## License

MIT License. See `LICENSE` for details.

## Contributing

Contributions are welcome. Please open an issue or a pull request with a clear description and steps to reproduce for bugs.

## Security and privacy

Downloaded audio is stored in the system temporary directory and removed after processing. API keys are read from environment variables or YAML at runtime and are not persisted by the library.


## Advanced usage (library)

YtQGen can be used as both a CLI and a Python library. The following examples show how to use individual components programmatically.

### Transcription (audio to text)

```python
from ytqgen.transcription import transcribe_audio
import os

# Configure transcription provider
os.environ['TRANSCRIPTION_PROVIDER'] = 'openai'  # or 'groq', 'mistral'
os.environ['OPENAI_API_KEY'] = 'your-key'

# Transcribe audio file (automatically handles chunking for long files)
transcript = transcribe_audio(
  audio_file_path='lecture.mp3',
  chunk_duration_minutes=10  # Optional: chunk size for long audio
)

print(f"Transcript: {transcript[:200]}...")
```

Features
- Supports OpenAI Whisper, Groq Whisper, Mistral Voxtral
- Automatic chunking for audio longer than 10 minutes
- Returns full transcription as text

### Summarization (long text to summary)

```python
from ytqgen.summarizer import summarize_text
import os

# Configure LLM provider
os.environ['LLM_PROVIDER'] = 'groq'
os.environ['GROQ_API_KEY'] = 'your-key'

# Summarize long transcription
summary = summarize_text(
  transcription=transcript,
  max_tokens=2048  # Optional: control summary length
)

print(f"Summary: {summary}")
```

Features
- Automatic chunking for very long texts (>1000 words)
- Hierarchical summarization (chunk, combine, final)
- Token-based length control

### Question generation

```python
from ytqgen.questionnaire_generator import generate_questionnaire
import os

# Configure LLM provider (if not already set)
os.environ['LLM_PROVIDER'] = 'openai'
os.environ['OPENAI_API_KEY'] = 'your-key'

# Configure question distribution (optional)
os.environ['YTQGEN_NUM_QUESTIONS'] = '20'
os.environ['YTQGEN_MCQ_PERCENTAGE'] = '70'
os.environ['YTQGEN_SUBJECTIVE_PERCENTAGE'] = '30'

# Generate questionnaire from summary
questionnaire = generate_questionnaire(summary=summary)

print(questionnaire)
```

Features
- MCQ (multiple choice) and subjective questions
- Configurable question counts and difficulty distribution
- Scenario-based and theoretical questions

### PDF generation

```python
from ytqgen.pdf_generator import save_text_as_pdf

# Save questionnaire as PDF
save_text_as_pdf(
  text=questionnaire,
  filename='my_quiz.pdf'
)

print("PDF saved successfully.")
```

### LLM client (direct access)

```python
from ytqgen.model_loader import get_llm_client
import os

os.environ['LLM_PROVIDER'] = 'mistral'
os.environ['MISTRAL_API_KEY'] = 'your-key'

# Get initialized LLM client
client, model_name, provider = get_llm_client()

print(f"Using {provider} with model {model_name}")

# Use client directly for custom tasks
if provider == 'mistral':
  response = client.chat.complete(
    model=model_name,
    messages=[{"role": "user", "content": "Explain AI"}]
  )
else:  # OpenAI or Groq
  response = client.chat.completions.create(
    model=model_name,
    messages=[{"role": "user", "content": "Explain AI"}]
  )
```

## Complete custom workflow example

```python
import os
from ytqgen.transcription import transcribe_audio
from ytqgen.summarizer import summarize_text
from ytqgen.questionnaire_generator import generate_questionnaire
from ytqgen.pdf_generator import save_text_as_pdf

# 1. Configure environment
os.environ['LLM_PROVIDER'] = 'groq'
os.environ['GROQ_API_KEY'] = 'gsk_...'
os.environ['TRANSCRIPTION_PROVIDER'] = 'groq'
os.environ['YTQGEN_NUM_QUESTIONS'] = '15'

# 2. Transcribe
print("Step 1: Transcribing...")
transcript = transcribe_audio('lecture.mp3')

# 3. Summarize
print("Step 2: Summarizing...")
summary = summarize_text(transcript, max_tokens=2048)

# 4. Generate questions
print("Step 3: Generating questions...")
questionnaire = generate_questionnaire(summary)

# 5. Save as PDF
print("Step 4: Creating PDF...")
save_text_as_pdf(questionnaire, 'custom_quiz.pdf')

print("Complete. Your quiz is ready.")
```

## Configuration via environment variables

All configuration can be done via environment variables:

```python
import os

# Provider selection
os.environ['LLM_PROVIDER'] = 'groq'  # openai, mistral, groq
os.environ['TRANSCRIPTION_PROVIDER'] = 'groq'

# API keys
os.environ['OPENAI_API_KEY'] = 'sk-...'
os.environ['MISTRAL_API_KEY'] = 'mistral-...'
os.environ['GROQ_API_KEY'] = 'gsk_...'

# Model selection (optional)
os.environ['YTQGEN_OPENAI_MODEL'] = 'gpt-4o'
os.environ['YTQGEN_MISTRAL_MODEL'] = 'mistral-large-latest'
os.environ['YTQGEN_GROQ_MODEL'] = 'llama-3.3-70b-versatile'

# Question generation settings
os.environ['YTQGEN_NUM_QUESTIONS'] = '20'
os.environ['YTQGEN_MCQ_PERCENTAGE'] = '70'
os.environ['YTQGEN_SUBJECTIVE_PERCENTAGE'] = '30'
os.environ['YTQGEN_EASY_PERCENTAGE'] = '30'
os.environ['YTQGEN_MEDIUM_PERCENTAGE'] = '50'
os.environ['YTQGEN_HARD_PERCENTAGE'] = '20'

# Summary settings
os.environ['YTQGEN_SUMMARY_MAX_LENGTH'] = '500'  # words
os.environ['YTQGEN_SUMMARY_MIN_LENGTH'] = '100'  # words
```

## Error handling

```python
from ytqgen.transcription import transcribe_audio

try:
  transcript = transcribe_audio('lecture.mp3')
except ValueError as e:
  print(f"Configuration error: {e}")
  # Handle missing API keys, invalid providers, etc.
except Exception as e:
  print(f"Transcription failed: {e}")
  # Handle API errors, file errors, etc.
```

Common errors
- ValueError: Missing API keys, invalid provider names
- FileNotFoundError: Audio file does not exist
- API errors: rate limits, invalid keys, network issues

## Use cases

### Batch processing multiple files

```python
import os
from ytqgen import process_stream

os.environ['LLM_PROVIDER'] = 'groq'
os.environ['GROQ_API_KEY'] = 'your-key'
os.environ['TRANSCRIPTION_PROVIDER'] = 'groq'

files = ['lecture1.mp4', 'lecture2.mp4', 'lecture3.mp4']

for i, file in enumerate(files, 1):
  print(f"\nProcessing {i}/{len(files)}: {file}")
  output = f'quiz_{i}.pdf'
  try:
    process_stream(file, output)
    print(f"Saved to {output}")
  except Exception as e:
    print(f"Failed: {e}")
```

### Custom question distribution

```python
import os
from ytqgen.questionnaire_generator import generate_questionnaire

# More MCQs, fewer subjective
os.environ['YTQGEN_NUM_QUESTIONS'] = '25'
os.environ['YTQGEN_MCQ_PERCENTAGE'] = '80'
os.environ['YTQGEN_SUBJECTIVE_PERCENTAGE'] = '20'

# Harder questions
os.environ['YTQGEN_EASY_PERCENTAGE'] = '20'
os.environ['YTQGEN_MEDIUM_PERCENTAGE'] = '40'
os.environ['YTQGEN_HARD_PERCENTAGE'] = '40'

questionnaire = generate_questionnaire(summary)
```

### Multi-provider workflow

```python
import os
from ytqgen.transcription import transcribe_audio
from ytqgen.summarizer import summarize_text
from ytqgen.questionnaire_generator import generate_questionnaire

# Use Groq for transcription
os.environ['TRANSCRIPTION_PROVIDER'] = 'groq'
os.environ['GROQ_API_KEY'] = 'gsk_...'

transcript = transcribe_audio('lecture.mp3')

# Use OpenAI for summarization and questions
os.environ['LLM_PROVIDER'] = 'openai'
os.environ['OPENAI_API_KEY'] = 'sk-...'
os.environ['YTQGEN_OPENAI_MODEL'] = 'gpt-4o'

summary = summarize_text(transcript)
questionnaire = generate_questionnaire(summary)
```

## API reference

### `process_stream(video_url, output_pdf)`

Full pipeline to process video/audio and generate a questionnaire.

Parameters
- `video_url` (str): Path to local file or YouTube URL
- `output_pdf` (str): Output PDF filename

Returns
- `str`: Path to generated PDF, or `None` if error

Environment variables used
- `LLM_PROVIDER`, `TRANSCRIPTION_PROVIDER`
- API keys for selected providers
- All `YTQGEN_*` configuration variables

### `transcribe_audio(audio_file_path, chunk_duration_minutes=10)`

Transcribe audio to text using API services.

Parameters
- `audio_file_path` (str): Path to audio/video file
- `chunk_duration_minutes` (int): Chunk size for long audio (default: 10)

Returns
- `str`: Transcribed text

Environment variables used
- `TRANSCRIPTION_PROVIDER` (default: 'openai')
- `OPENAI_API_KEY`, `GROQ_API_KEY`, or `MISTRAL_API_KEY`

### `summarize_text(transcription, max_tokens=MAX_TOKENS)`

Summarize long transcription text.

Parameters
- `transcription` (str): Input text to summarize
- `max_tokens` (int): Maximum tokens for summary (default: 2048)

Returns
- `str`: Summarized text

Environment variables used
- `LLM_PROVIDER`
- API key for selected provider
- `YTQGEN_SUMMARY_MAX_LENGTH`, `YTQGEN_SUMMARY_MIN_LENGTH`

### `generate_questionnaire(summary)`

Generate a structured questionnaire from a summary.

Parameters
- `summary` (str): Summarized content

Returns
- `str`: Formatted questionnaire with questions and answers

Environment variables used
- `LLM_PROVIDER`
- API key for selected provider
- `YTQGEN_NUM_QUESTIONS`
- `YTQGEN_MCQ_PERCENTAGE`, `YTQGEN_SUBJECTIVE_PERCENTAGE`
- `YTQGEN_EASY_PERCENTAGE`, `YTQGEN_MEDIUM_PERCENTAGE`, `YTQGEN_HARD_PERCENTAGE`

### `save_text_as_pdf(text, filename)`

Save text content as a formatted PDF.

Parameters
- `text` (str): Content to save
- `filename` (str): Output PDF path

Returns
- `None`

### `get_llm_client()`

Get an initialized LLM client for the selected provider.

Returns
- `tuple`: (client, model_name, provider)
  - `client`: Initialized API client object
  - `model_name` (str): Model being used
  - `provider` (str): Provider name ('openai', 'mistral', 'groq')

Environment variables used
- `LLM_PROVIDER`
- API key for selected provider
- `YTQGEN_OPENAI_MODEL`, `YTQGEN_MISTRAL_MODEL`, `YTQGEN_GROQ_MODEL`

## When to use library vs CLI

Use CLI when
- Quick one-off processing
- Simple workflows
- YAML configuration preferred
- Running from terminal or scripts

Use Python library when
- Custom processing pipelines
- Integration with existing Python applications
- Batch processing multiple files
- Need fine-grained control over each step
- Building applications on top of YtQGen
- Programmatic configuration required

Questions or issues?

Open an issue on [GitHub](https://github.com/JaiAnshSB26/YtQGen_Public/issues) or contact us at [team@jbac.dev](mailto:team@jbac.dev).
