Skip to content

refactor: unify media extraction and secure credential lifecycle - #10

Merged
ztygod merged 2 commits into
mainfrom
feat/cookie-and-Whisper
Aug 15, 2026
Merged

refactor: unify media extraction and secure credential lifecycle#10
ztygod merged 2 commits into
mainfrom
feat/cookie-and-Whisper

Conversation

@ztygod

@ztygod ztygod commented Aug 13, 2026

Copy link
Copy Markdown
Owner

变更概述

重构媒体获取架构,将 YouTube 和 Bilibili 的元数据、格式、播放列表、字幕、音频及视频能力统一收口到 noteforge.media

主要变更

  • 新增统一 MediaService 公共 API,屏蔽 yt-dlp 参数和平台实现细节
  • 删除原 noteforge.collector 模块,CLI 与核心流水线全部迁移到新媒体服务
  • 使用平台适配器统一 YouTube 和 Bilibili 数据结构
  • 使用独立 Worker 进程池隔离 yt-dlp 执行
  • 新增 MediaAsset,通过 TTL 和上下文管理控制临时媒体生命周期
  • 媒体默认在任务结束后删除,仅允许通过 export_to() 显式持久化
  • 新增独立 CookieService,负责 Cookie 获取、过滤、更新、保留和删除
  • Cookie 默认随任务结束删除;用户选择保留时使用 AES-GCM 加密
  • Cookie Vault 主密钥由系统 Keyring 管理
  • 使用平台域名白名单限制可进入租约和 Vault 的 Cookie
  • 将非敏感元数据和标准化转录缓存重构为 MediaRepository
  • AudioTranscriberMediaWorker 接口集中到 protocols.py
  • 补充关键代码和属性的中文注释
  • 更新配置示例、依赖锁文件及中英文文档

安全设计

  • 调用方无法直接传递或感知 yt-dlp 参数
  • yt-dlp 不再直接使用 cookiesfrombrowser
  • Cookie 明文只存在于权限受限的临时租约目录
  • Cookie 文件权限为 0600,租约目录权限为 0700
  • 临时 Cookie 在任务结束后立即删除
  • 持久 Cookie 使用认证加密,不允许降级为明文存储
  • 媒体文件与 Cookie 使用彼此独立的生命周期
  • 支持清理异常退出后遗留的过期租约

验证

  • Ruff 格式检查通过
  • Ruff 静态检查通过
  • git diff --check 通过
  • 完整测试通过:205 passed

@ztygod ztygod changed the title feat: move render to documnet refactor: unify media extraction and secure credential lifecycle Aug 15, 2026
@ztygod
ztygod requested a lite review from Copilot August 15, 2026 02:27

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors NoteForge’s media layer by consolidating YouTube/Bilibili extraction behind a single noteforge.media boundary, while introducing a more secure, lease-based lifecycle for temporary media and cookies (with optional encrypted retention via keyring).

Changes:

  • Introduces MediaService + platform adapters + isolated worker execution to unify metadata/formats/playlists/subtitles/audio/video without exposing yt-dlp options.
  • Adds lease-scoped MediaAsset and a new CookieService that filters domains and supports AEAD-encrypted retention with a keyring-managed master key.
  • Removes the legacy noteforge.collector module, migrates CLI/pipeline/tests to the new APIs, and updates docs/config and dependencies.

Reviewed changes

Copilot reviewed 46 out of 47 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
uv.lock Adds cryptography/keyring and related transitive deps to support encrypted credential retention.
tests/media/test_media.py Updates media tests to cover adapters, cookie filtering, assets, service download behavior, and repository round-trip.
tests/document/test_markdown_renderer.py Updates imports to use noteforge.document as the public renderer/writer API.
tests/collector/test_inspection.py Migrates inspection tests to noteforge.media.source.
tests/collector/test_bilibili.py Migrates Bilibili tests from collector to the new adapter API.
tests/cli/test_app.py Updates CLI test imports to use noteforge.media.source.
src/noteforge/renderer/init.py Removes legacy renderer package entrypoint (API moved under noteforge.document).
src/noteforge/media/ytdlp/errors.py Adds clarification docstring and keeps download error translation centralized.
src/noteforge/media/ytdlp/client.py Hardens yt-dlp defaults (no browser cookie reading by options), adds explicit cookiefile injection only via service/worker.
src/noteforge/media/worker.py Adds process-isolated worker implementation (plus in-process variant for tests).
src/noteforge/media/transcriber.py Removes old transcriber protocol module (moved to protocols.py).
src/noteforge/media/subtitle.py Adds clarifying docstrings around parsing and asset path expectations.
src/noteforge/media/source.py Adds local URL inspection/normalization for YouTube/Bilibili with Bilibili page handling.
src/noteforge/media/service.py Adds the unified MediaService orchestration layer, asset lifecycle, cookie lease usage, and CLI helper functions.
src/noteforge/media/repository.py Renames cache to MediaRepository and clarifies scope (only small non-sensitive normalized data).
src/noteforge/media/protocols.py Centralizes AudioTranscriber and MediaWorker protocols.
src/noteforge/media/platforms/youtube.py Adds YouTube adapter implementation (currently minimal backend options).
src/noteforge/media/platforms/bilibili.py Adds Bilibili adapter supplying required headers for requests.
src/noteforge/media/platforms/base.py Adds the platform adapter base mapping raw yt-dlp structures into stable domain models.
src/noteforge/media/platforms/init.py Exposes platform adapter types via package exports.
src/noteforge/media/models.py Expands domain models (formats, requests, assets/auth enums) and clarifies the “no yt-dlp options leak” contract.
src/noteforge/media/cookies/service.py Implements cookie leasing, domain filtering, encrypted retention, keyring-backed master key, and cleanup.
src/noteforge/media/cookies/policy.py Adds per-platform cookie domain allowlists.
src/noteforge/media/cookies/init.py Exposes cookie service/lease/index types via package exports.
src/noteforge/media/config.py Refactors extractor config to separate network proxy config from credential handling and adds runtime/vault/worker settings.
src/noteforge/media/assets.py Adds TTL-scoped MediaAsset with explicit persistence via export_to().
src/noteforge/media/init.py Defines the new public media API surface (MediaService, models, cookie types, inspection types).
src/noteforge/document/writer.py Updates writer module docstring and aligns it with the document package boundary.
src/noteforge/document/renderers/markdown.py Fixes imports to avoid circularity and anchors renderer to document.models.
src/noteforge/document/renderers/init.py Introduces renderers package export for MarkdownRenderer.
src/noteforge/document/init.py Exposes MarkdownRenderer and write_markdown from the document package.
src/noteforge/core/pipeline.py Migrates pipeline to noteforge.media and noteforge.document public APIs.
src/noteforge/collector/source/inspection.py Removes legacy source inspection implementation (replaced by noteforge.media.source).
src/noteforge/collector/source/init.py Removes legacy source module exports.
src/noteforge/collector/platforms/youtube.py Removes legacy YouTube collector.
src/noteforge/collector/platforms/bilibili.py Removes legacy Bilibili collector.
src/noteforge/collector/platforms/base.py Removes legacy platform collector base implementation.
src/noteforge/collector/platforms/init.py Removes legacy collector platform exports.
src/noteforge/collector/factory.py Removes legacy collector factory and CLI-facing wrappers (replaced by media service helpers).
src/noteforge/collector/init.py Removes legacy collector package exports.
src/noteforge/cli/commands/inspect.py Migrates CLI inspect command to noteforge.media.
src/noteforge/cli/commands/generate.py Migrates CLI generate command to noteforge.media.
src/noteforge/cli/commands/doctor.py Migrates CLI doctor command to noteforge.media.
README.zh-CN.md Updates Chinese docs to describe the unified media service, worker isolation, and secure cookie lifecycle.
README.md Adds English docs for media service + credentials (needs internal consistency update).
pyproject.toml Adds cryptography and keyring runtime dependencies.
config.example.yaml Updates example config to use runtime/vault/worker settings and removes plaintext cookie file options.
Suppressed comments (2)

src/noteforge/media/cookies/service.py:285

  • _new_lease_root() creates the per-task lease directory via tempfile.mkdtemp() but does not explicitly set the lease directory permission to 0700. That weakens the stated security guarantee (lease dir 0700, cookie file 0600) on platforms/filesystems where mkdtemp() may be affected by the process umask.
        self.runtime_root.mkdir(parents=True, exist_ok=True, mode=0o700)
        os.chmod(self.runtime_root, 0o700)
        return Path(tempfile.mkdtemp(prefix="lease-", dir=self.runtime_root))

src/noteforge/media/cookies/service.py:341

  • _vault_key() assumes the keyring value is valid hex and calls bytes.fromhex(stored) without error handling. If the keyring entry is corrupted/non-hex (or from an older format), this will raise ValueError and leak an implementation detail instead of a clear CookieSecurityError path.
        stored = keyring.get_password(service, account)
        if stored is None:
            stored = secrets.token_hex(32)
            keyring.set_password(service, account, stored)
        return bytes.fromhex(stored)

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

)
self.vault_root = vault_root or Path.home() / ".noteforge" / "credentials"
self.lease_ttl = lease_ttl # 仅用于回收异常退出后的残留租约。
self._lock = threading.RLock()

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +379 to +381
def _asset_root(self) -> Path:
self.config.runtime_path.mkdir(parents=True, exist_ok=True)
return Path(tempfile.mkdtemp(prefix="asset-", dir=self.config.runtime_path))

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread README.md
Comment on lines 51 to +55
NoteForge downloads subtitles only. It does not download the video or audio.

## Media service and credentials

`noteforge.media.MediaService` is the sole media boundary for YouTube and
@ztygod
ztygod merged commit 1704e98 into main Aug 15, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants