NotebookLM — Document Intelligence & Podcast Generator
Обёртка над Python-библиотекой notebooklm-py.
Что понадобится
| Нужно | Платно? | Где взять |
|---|---|---|
| Google-аккаунт | нет | NotebookLM бесплатен для личного использования |
pip install "notebooklm-py[browser]" |
нет | PyPI; [browser] тянет Playwright + ~170 МБ Chromium при первом login |
| Один интерактивный вход | нет | notebooklm login — открывает браузер, ты логинишься руками |
Ключей API нет и не бывает: библиотека работает через недокументированные внутренние эндпоинты Google и живую сессию браузера. Отсюда два следствия, с которыми придётся жить: эндпоинты меняются без предупреждения (ниже — таблица переименований команд, которые уже случились), а сессия протухает посреди долгой генерации.
Сессия лежит в ~/.notebooklm/ и в git не попадает — но при шаринге конфига проверь,
что этой папки нет в архиве: там живые cookie твоего Google-аккаунта.
When to Use
- "сделай подкаст из [документов]" — audio overview generation
- "deep research из [URL/PDF]" — grounded answers with citations
- "quiz/flashcards из [материала]" — learning materials
- "mind map из [темы]" — visual knowledge graphs
- "загрузи в NotebookLM" — add sources to notebook
- "что говорится в документе о [тема]" — Q&A over documents
Prerequisites
pip install "notebooklm-py[browser]"
# First-time auth (one-time, interactive in real terminal):
notebooklm login
# Opens Chromium → user logs into Google → press ENTER → saves to ~/.notebooklm/storage_state.json
CRITICAL: stdin is required for interactive login
The CLI uses input("Press ENTER") which fails in piped/background bash sessions with
RuntimeError: lost sys.stdin. Workarounds:
- Real terminal (TTY) — PowerShell, Terminal.app, любой интерактивный shell — preferred
- Headed Playwright auto-detect (no TTY needed) — see "Headed Login" below
Auth file structure
~/.notebooklm/browser_profile/— Chromium persistent profile (long-lived Google session, weeks)~/.notebooklm/storage_state.json— short-lived cookie snapshot used by CLI (~1h TTL)
The CLI reads storage_state.json for every command. When it expires, you get:
Error: Authentication expired or invalid. Redirected to: https://accounts.google.com/...
Long-running operations (artifact wait for 10+ min, multilang loops) WILL hit this.
Solution: refresh tokens between calls — или обновиться до 0.7.x, где есть self-healing
master-token (см. references/upgrade-v0.7-capabilities.md).
Headless refresh (no TTY required)
Если сессия в browser_profile ещё жива, обновить storage_state.json программно:
# refresh-nb-auth.py
import sys, asyncio
from pathlib import Path
if sys.platform == "win32":
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
from playwright.sync_api import sync_playwright
HOME = Path.home() / ".notebooklm"
with sync_playwright() as p:
ctx = p.chromium.launch_persistent_context(
user_data_dir=str(HOME / "browser_profile"),
headless=True,
args=["--disable-blink-features=AutomationControlled", "--password-store=basic"],
ignore_default_args=["--enable-automation"],
)
page = ctx.pages[0] if ctx.pages else ctx.new_page()
page.goto("https://notebooklm.google.com/", wait_until="load", timeout=30000)
ctx.storage_state(path=str(HOME / "storage_state.json"))
ctx.close()
Запускать перед каждой командой CLI в длинном цикле. Если и профиль протух, скрипт вернёт
URL вида accounts.google.com/signin/... и сохранит неполный state — тогда нужен реальный
логин (следующий раздел).
Headed Login with auto-detect (no manual ENTER)
Когда browser_profile протух и нужен новый вход, а терминал не интерактивный.
Скрипт открывает окно, ждёт, пока человек прокликает логин Google, и сам ловит момент.
# login-nb.py
import sys, time, asyncio
from pathlib import Path
if sys.platform == "win32":
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
from playwright.sync_api import sync_playwright
HOME = Path.home() / ".notebooklm"
HOME.mkdir(parents=True, exist_ok=True)
with sync_playwright() as p:
ctx = p.chromium.launch_persistent_context(
user_data_dir=str(HOME / "browser_profile"),
headless=False, # окно нужно человеку
args=["--disable-blink-features=AutomationControlled", "--password-store=basic"],
ignore_default_args=["--enable-automation"],
)
page = ctx.pages[0] if ctx.pages else ctx.new_page()
page.goto("https://notebooklm.google.com/", wait_until="load", timeout=60000)
deadline = time.monotonic() + 240 # 4-минутное окно на вход
ok = False
while time.monotonic() < deadline:
url = page.url
if "notebooklm.google.com" in url and "signin" not in url:
if "SID" in [c["name"] for c in ctx.cookies()]:
time.sleep(3) # дать досесться ленивым XHR
ctx.storage_state(path=str(HOME / "storage_state.json"))
ok = True
break
time.sleep(2)
ctx.close()
sys.exit(0 if ok else 1)
Человек просто проходит логин в открывшемся окне — взаимодействия с терминалом не требуется.
Проверка cookie SID обязательна: страница успевает отрисоваться раньше, чем сессия
становится валидной, и без неё сохранится пустой state.
Windows-specific: Unicode + path issues
# Always set this for any notebooklm CLI call
export PYTHONIOENCODING=utf-8
# Without it: 'charmap' codec can't encode '✓' / 'Русский' → Unicode crash on Windows console
# Node.js on Windows can't open POSIX paths like /tmp/...
# Use TMPDIR or convert with cygpath -m before passing to node:
TMPDIR_WIN="${TMPDIR:-${TEMP:-$HOME/AppData/Local/Temp}}"
SRC_PATH_NODE=$(cygpath -m "$SRC_PATH" 2>/dev/null || echo "$SRC_PATH")
node -e "fs.readFileSync('$SRC_PATH_NODE')"
Input Validation (MANDATORY)
Before passing ANY user input to NotebookLM, validate:
import re
def validate_input(text: str) -> str:
"""Sanitize input before sending to NotebookLM API."""
# Block prompt injection patterns
INJECTION_PATTERNS = [
r'ignore\s+(all\s+)?previous\s+instructions',
r'disregard\s+(all\s+)?previous',
r'override\s+(system|previous)',
r'you\s+are\s+now\s+',
r'pretend\s+(?:you|to\s+be)',
r'</?(?:system|assistant|human)>',
r'\[SYSTEM\]',
r'\[INST\]',
]
for pattern in INJECTION_PATTERNS:
if re.search(pattern, text, re.IGNORECASE):
raise ValueError("Blocked: potential injection pattern detected")
# Limit length (NotebookLM has own limits, but defense-in-depth)
if len(text) > 10000:
text = text[:10000]
return text.strip()
def validate_url(url: str) -> str:
"""Validate URL before adding as source."""
from urllib.parse import urlparse
parsed = urlparse(url)
if parsed.scheme not in ('http', 'https'):
raise ValueError(f"Invalid URL scheme: {parsed.scheme}")
# Block local/private URLs
if parsed.hostname in ('localhost', '127.0.0.1', '0.0.0.0'):
raise ValueError("Local URLs not allowed")
if parsed.hostname and parsed.hostname.startswith('192.168.'):
raise ValueError("Private network URLs not allowed")
return url
CLI Operations
Create notebook and add sources
# Create a new notebook
notebooklm create "Research: AI Agents 2026"
# Add sources — сигнатура `source add [OPTIONS] CONTENT`, тип определяется сам
notebooklm source add "https://example.com/article" -n NOTEBOOK_ID
notebooklm source add "/path/to/document.pdf" -n NOTEBOOK_ID --type file
notebooklm source add "https://youtube.com/watch?v=VIDEO_ID" -n NOTEBOOK_ID --type youtube
Группы
notebookв CLI 0.3.4 НЕТ (create— команда верхнего уровня), а уsource addнет ни--notebook-id, ни--url/--file: только-n/--notebook,--type [url|text|file|youtube],--title,--mime-type. Заглавный токенURL/YOUTUBEуйдёт в CONTENT и создаст текстовый источник со словом «URL» внутри.
Query notebook (grounded answers)
# Группы `chat` в CLI нет — `ask` это команда верхнего уровня
notebooklm ask "What are the key findings about AI agents?" -n NOTEBOOK_ID
Generate studio artifacts
Структура CLI менялась; ниже — команды, проверенные на 0.3.4:
# Audio podcast — preferred for content discussion
notebooklm generate audio --json -n NB_ID --format deep-dive --length long "Description guides hosts"
# Format options:
# deep-dive — TWO conversational hosts (recommended for podcasts)
# brief — single-host short summary (1-3 min)
# critique — analytical breakdown
# debate — opposing viewpoints
# Length options:
# short — ~1-3 min (truncates aggressively)
# default — ~3-7 min
# long — ~10-25 min (use for substantial content)
# Returns task_id in JSON. Use --json to parse.
# Other artifact types
notebooklm generate report -n NB_ID
notebooklm generate slide-deck -n NB_ID
notebooklm generate mind-map -n NB_ID
notebooklm generate quiz -n NB_ID
notebooklm generate flashcards -n NB_ID
notebooklm generate infographic -n NB_ID
notebooklm generate data-table -n NB_ID
notebooklm generate cinematic-video -n NB_ID
notebooklm generate video -n NB_ID
Wait for completion + download
# Block until ready (CRITICAL for scripts — generation takes 5-25 min)
notebooklm artifact wait TASK_ID -n NB_ID --timeout 900 --interval 15 --json
# Status check (no wait, returns current state)
notebooklm artifact poll TASK_ID -n NB_ID
# Download — uses notebook context, picks latest of given type
notebooklm download audio /path/to/output.mp3 -n NB_ID
notebooklm download mind-map /path/to/output.png -n NB_ID
# IMPORTANT: --output is positional in new CLI. Pass full path as ARG, not flag.
CLI command renames (old docs → current)
Эти переименования — прямое следствие того, что API недокументированный. Если команда из
чужой инструкции не находится, сверься с таблицей и с notebooklm <group> --help.
| Old | New |
|---|---|
notebooklm notebook create "T" |
notebooklm create "T" |
notebooklm studio generate ID audio_overview |
notebooklm generate audio -n ID |
notebooklm studio download ID audio_overview --output P |
notebooklm download audio P -n ID |
notebooklm studio status ID audio_overview |
notebooklm artifact poll TASK_ID -n ID |
notebooklm source add ID --file P --type text |
notebooklm source add P -n ID --type text --title T |
Python API (for advanced workflows)
import asyncio
from notebooklm import NotebookLMClient
async def research_and_podcast(topic, urls):
async with await NotebookLMClient.from_storage() as client:
# Create notebook
nb = await client.notebooks.create(f"Research: {topic}")
# Add sources
for url in urls:
validated_url = validate_url(url) # ALWAYS validate
await client.sources.add_url(nb.id, validated_url)
# Ask questions
question = validate_input(f"Summarize key insights about {topic}")
answer = await client.chat.ask(nb.id, question)
print(answer.text)
# Generate podcast — атрибута `client.studio` НЕТ; артефакты живут в client.artifacts,
# и обобщённых generate/download там тоже нет, только по типам
status = await client.artifacts.generate_audio(nb.id, audio_format="deep-dive",
audio_length="long")
await client.artifacts.wait_for_completion(nb.id, status.task_id, timeout=1800)
# Download — output_path позиционный, каталога не хватит, нужен полный путь к файлу
await client.artifacts.download_audio(nb.id, "~/Documents/NotebookLM/podcast.mp3")
asyncio.run(research_and_podcast("AI Agents", [
"https://arxiv.org/abs/2405.12345",
"https://example.com/agent-patterns"
]))
Language management
Output language is a GLOBAL account-level setting, NOT per-notebook:
notebooklm language list # show all 30+ supported codes
notebooklm language get # current default
notebooklm language set ru # set default
# Codes that need region suffix:
# pt_BR — Portuguese Brazilian
# pt_PT — Portuguese Portugal
# ar_001 — Modern Standard Arabic
# ar_eg — Egyptian Arabic
# es_419 — Latin American Spanish
# fr_CA — Canadian French
# zh_Hans — Simplified Chinese
# zh_Hant — Traditional Chinese
For multilang generation: switch language → generate → wait → download → switch next. Generations themselves take 5-25 min, language switch is instant.
# Multilang loop pattern
for lang_full in ru en es pt_BR fr ar_001; do
notebooklm language set "$lang_full"
notebooklm generate audio --json -n "$NB_ID" --format deep-dive --length long "Cover all sources" \
| jq -r '.task_id' > /tmp/last_task.txt
notebooklm artifact wait "$(cat /tmp/last_task.txt)" -n "$NB_ID" --timeout 1800 --interval 20 --json
notebooklm download audio "/tmp/podcast-$lang_full.mp3" -n "$NB_ID"
done
Такой цикл — самый частый способ упереться в истечение сессии: шесть языков по 10-25 минут
это два-три часа. Ставь refresh-nb-auth.py перед каждой итерацией или обновись до 0.7.x.
Content quality matters: titles ≠ podcast material
Проверено на практике: ведущие обсуждают только то, что ЕСТЬ в источнике. Дашь им заголовки и превью по 150 символов — они двенадцать минут будут рассуждать вокруг названия темы без единой конкретики. На слух это ровно тот случай, когда сразу понятно, что говорит машина, и слушать нечего.
Плохо (ведущим не за что зацепиться):
## OpenAI Codex
_Source: TechCrunch_
OpenAI released a new Codex guide.
Хорошо (у ведущих есть материал):
## Новость 1: OpenAI выпустила руководство по Codex
_Источник: TechCrunch_
**Краткая суть:** OpenAI опубликовала практический гид по настройке workspace, проектов и потоков задач для разработчиков.
[FULL 500-800 word article body with names, numbers, quotes, product details]
Всегда клади в источник полный текст, а не только заголовки и метаданные. Для содержательного обсуждения на 10-15 минут ноутбуку нужно 30-100 КБ реального контента.
Format & length to runtime mapping (empirical)
| Format | Length | Hosts | Typical runtime | Use case |
|---|---|---|---|---|
brief |
short |
1 | 1-3 min | Quick TLDR |
brief |
default |
1 | 3-5 min | Single article scan |
deep-dive |
default |
2 | 5-10 min | Single article discussion |
deep-dive |
long |
2 | 10-25 min | Daily digest, multi-source |
debate |
any | 2 | varies | Pros/cons takes |
critique |
any | 1 | varies | Analytical breakdown |
Note: NotebookLM игнорирует подсказку длины, если контента мало. Замеры: источник на
591 слово с --length long дал ~22 минуты (ведущие растягивали), источник на 44 КБ с теми же
флагами — 14 минут (плотнее, меньше воды).
Workflow: Document → Podcast Pipeline
- Collect sources: URLs, PDFs, YouTube videos about topic
- Validate inputs: All URLs through
validate_url(), all text throughvalidate_input() - Create notebook:
notebooklm create "Topic Name" - Add sources:
notebooklm source add "<url|path|text>" -n NB_IDfor each source - Generate podcast:
notebooklm generate audio --json -n NB_ID --format deep-dive --length long - Download MP3:
notebooklm artifact wait TASK_ID -n NB_ID --timeout 1800→notebooklm download audio /path/out.mp3 -n NB_ID - Save the summary рядом с MP3 в свою базу заметок
Workflow: интеграция со своей базой заметок
Пример на markdown-хранилище (Obsidian, Logseq, просто папка с .md). Подставь свой путь.
NOTES=~/notes # твоя база заметок
# 1. Создать ноутбук
notebooklm create "From notes: Topic"
# 2. Добавить заметку как текстовый источник (CLI умеет: путь к .md уедет как text)
notebooklm source add "$NOTES/research/topic.md" -n NOTEBOOK_ID --type text --title "Topic"
# 3. Сгенерировать артефакт
notebooklm generate mind-map -n NOTEBOOK_ID
# 4. Забрать результат обратно — путь ПОЗИЦИОННЫЙ и должен быть файлом, не каталогом
notebooklm download mind-map "$NOTES/research/topic-mindmap.png" -n NOTEBOOK_ID
Автоматизация по расписанию
Генерация идёт 5-25 минут и переживает истечение сессии плохо — поэтому в cron/планировщике
всегда ставь обновление токена перед вызовом и --timeout 1800, а не короткий.
Гоча Windows Task Scheduler, которая стоит отдельного упоминания: RepetitionDuration
обязан быть ограничен. [TimeSpan]::MaxValue даёт XML-ошибку P99999999DT23H59M59S
и задача не регистрируется. Используй New-TimeSpan -Days 365.
$action = New-ScheduledTaskAction -Execute "C:\Program Files\Git\bin\bash.exe" `
-Argument "-c 'export PYTHONIOENCODING=utf-8; bash ~/bin/my-podcast.sh >> /tmp/podcast-cron.log 2>&1'"
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(5) `
-RepetitionInterval (New-TimeSpan -Hours 12) `
-RepetitionDuration (New-TimeSpan -Days 365)
Register-ScheduledTask -TaskName "NotebookLMPodcast" -Action $action -Trigger $trigger
Output Directory
Default: ~/Documents/NotebookLM/. На Windows CLI переводит POSIX-пути (/tmp/...) в
C:\tmp\ через cygpath -m — если ждёшь файл не там, где он появился, дело в этом.
Credit/Usage Notes
- Google NotebookLM is free for personal use — платного ключа нет
- Unofficial API — may break with Google updates
- Rate limits apply (undocumented, typically ~50 requests/hour); multilang-циклы идут близко к границе
- Large documents may take time to process
- Audio podcasts take 5-25 minutes to generate (
--length long— ближе к верхней границе; ставь--timeout 1800, а не короткий)
Integration Chain
| Next Step | Skill |
|---|---|
| Research trending topics first | last30days |
| Transcribe podcast audio | deepgram |
| Translate content | deepl-pro |
| Create social post from research | tg-post, content-creation |
Safety
- All inputs validated through
validate_input()andvalidate_url()before API calls - No private/local URLs allowed as sources
- Prompt injection patterns blocked before transmission
- Session credentials stored locally in
~/.notebooklm/— не класть в git и не отдавать вместе с конфигом: это живые cookie твоего Google-аккаунта - Аккаунт используется обычный личный — библиотека не умеет иначе. Если это неприемлемо, заведи отдельный Google-аккаунт под NotebookLM
Дальше
references/upgrade-v0.7-capabilities.md — что даёт версия 0.7.x (self-healing master-token
вместо Playwright-костылей + встроенный MCP-сервер) и чек-лист безопасного апгрейда.