From ecc
비디오와 오디오를 보고, 이해하고, 행동합니다. 보기- 로컬 파일, URL, RTSP/라이브 피드 또는 데스크톱 라이브 녹화에서 수집하고 실시간 컨텍스트와 재생 가능한 스트림 링크를 반환합니다. 이해하기- 프레임을 추출하고, 시각적/의미적/시간적 인덱스를 빌드하며, 타임스탬프와 자동 클립으로 순간을 검색합니다. 행동하기- 트랜스코딩 및 정규화(코덱, fps, 해상도, 가로세로비), 타임라인 편집(자막, 텍스트/이미지 오버레이, 브랜딩, 오디오 오버레이, 더빙, 번역), 미디어 자산 생성(이미지, 오디오, 비디오), 라이브 스트림이나 데스크톱 캡처 이벤트에 대한 실시간 알림을 생성합니다.
npx claudepluginhub sam42-lab/everything-claude-code-krThis skill is limited to using the following tools:
**비디오, 라이브 스트림, 데스크톱 세션을 위한 인식 + 메모 + 액션.**
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
비디오, 라이브 스트림, 데스크톱 세션을 위한 인식 + 메모 + 액션.
VideoDB 코드를 실행하기 전에 프로젝트 디렉터리로 이동하여 환경 변수를 로드합니다.
from dotenv import load_dotenv
load_dotenv(".env")
import videodb
conn = videodb.connect()
이는 다음 위치에서 VIDEO_DB_API_KEY를 읽습니다.
.env 파일키가 누락된 경우 videodb.connect()는 자동으로 AuthenticationError를 발생시킵니다.
짧은 인라인 명령으로 충분할 때는 스크립트 파일을 작성하지 마세요.
인라인 Python(python -c "...")을 작성할 때는 항상 올바른 형식의 코드를 사용하세요. 세미콜론을 사용하여 문을 구분하고 가독성을 유지하세요. 약 3개 이상의 문장이 필요한 경우 대신 heredoc을 사용하세요.
python << 'EOF'
from dotenv import load_dotenv
load_dotenv(".env")
import videodb
conn = videodb.connect()
coll = conn.get_collection()
print(f"Videos: {len(coll.get_videos())}")
EOF
사용자가 "setup videodb" 등을 요청할 때:
pip install "videodb[capture]" python-dotenv
Linux에서 videodb[capture]가 실패하면 캡처 익스트라 없이 설치하세요.
pip install videodb python-dotenv
사용자는 다음 중 한 가지 방법을 사용하여 VIDEO_DB_API_KEY를 설정해야 합니다.
export VIDEO_DB_API_KEY=your-key.env 파일: 프로젝트의 .env 파일에 VIDEO_DB_API_KEY=your-key 저장console.videodb.io에서 무료 API 키를 받으세요 (신용카드 없이 50개 무료 업로드 가능).
직접 API 키를 읽거나 쓰고 처리하지 마세요. 항상 사용자가 설정하도록 하세요.
# URL
video = coll.upload(url="https://example.com/video.mp4")
# YouTube
video = coll.upload(url="https://www.youtube.com/watch?v=VIDEO_ID")
# 로컬 파일
video = coll.upload(file_path="/path/to/video.mp4")
# force=True는 비디오가 이미 인덱싱된 경우 오류를 건너뜁니다.
video.index_spoken_words(force=True)
text = video.get_transcript_text()
stream_url = video.add_subtitle()
from videodb.exceptions import InvalidRequestError
video.index_spoken_words(force=True)
# search()는 결과가 없을 때 InvalidRequestError를 발생시킵니다.
# 항상 try/except로 감싸고 "No results found"를 빈 결과로 처리하세요.
try:
results = video.search("제품 데모")
shots = results.get_shots()
stream_url = results.compile()
except InvalidRequestError as e:
if "No results found" in str(e):
shots = []
else:
raise
import re
from videodb import SearchType, IndexType, SceneExtractionType
from videodb.exceptions import InvalidRequestError
# index_scenes()는 force 매개변수가 없습니다. 장면 인덱스가 이미 존재하면
# 오류를 발생시킵니다. 오류에서 기존 인덱스 ID를 추출하세요.
try:
scene_index_id = video.index_scenes(
extraction_type=SceneExtractionType.shot_based,
prompt="이 장면의 시각적 내용을 설명하세요.",
)
except Exception as e:
match = re.search(r"id\s+([a-f0-9]+)", str(e))
if match:
scene_index_id = match.group(1)
else:
raise
# 관련성이 낮은 노이즈를 필터링하려면 score_threshold를 사용하세요(0.3 이상 권장).
try:
results = video.search(
query="화이트보드에 글을 쓰는 사람",
search_type=SearchType.semantic,
index_type=IndexType.scene,
scene_index_id=scene_index_id,
score_threshold=0.3,
)
shots = results.get_shots()
stream_url = results.compile()
except InvalidRequestError as e:
if "No results found" in str(e):
shots = []
else:
raise
중요: 타임라인을 빌드하기 전에 항상 타임스탬프를 검증하세요.
start는 0 이상이어야 합니다(음수 값은 암시적으로 수용되지만 손상된 출력이 생성됨).start는 end보다 작아야 합니다.end는 video.length보다 작거나 같아야 합니다.from videodb.timeline import Timeline
from videodb.asset import VideoAsset, TextAsset, TextStyle
timeline = Timeline(conn)
timeline.add_inline(VideoAsset(asset_id=video.id, start=10, end=30))
timeline.add_overlay(0, TextAsset(text="끝", duration=3, style=TextStyle(fontsize=36)))
stream_url = timeline.generate_stream()
from videodb import TranscodeMode, VideoConfig, AudioConfig
# 서버 측에서 해상도, 품질 또는 가로세로비 변경
job_id = conn.transcode(
source="https://example.com/video.mp4",
callback_url="https://example.com/webhook",
mode=TranscodeMode.economy,
video_config=VideoConfig(resolution=720, quality=23, aspect_ratio="16:9"),
audio_config=AudioConfig(mute=False),
)
경고: reframe()은 느린 서버 측 작업입니다. 긴 비디오의 경우
몇 분이 걸릴 수 있으며 시간이 초과될 수 있습니다. 모범 사례:
start/end를 사용하여 짧은 구간으로 제한하세요.callback_url을 사용하세요.Timeline에서 비디오를 트리밍한 다음 짧아진 결과를 리프레임하세요.from videodb import ReframeMode
# 항상 짧은 구간을 리프레임하는 것을 선호하세요:
reframed = video.reframe(start=0, end=60, target="vertical", mode=ReframeMode.smart)
# 전체 길이 비디오를 위한 비동기 리프레임 (None 반환, 웹훅을 통한 결과):
video.reframe(target="vertical", callback_url="https://example.com/webhook")
# 프리셋: "vertical" (9:16), "square" (1:1), "landscape" (16:9)
reframed = video.reframe(start=0, end=60, target="square")
# 사용자 정의 크기
reframed = video.reframe(start=0, end=60, target={"width": 1280, "height": 720})
image = coll.generate_image(
prompt="산 너머로 지는 석양",
aspect_ratio="16:9",
)
from videodb.exceptions import AuthenticationError, InvalidRequestError
try:
conn = videodb.connect()
except AuthenticationError:
print("VIDEO_DB_API_KEY를 확인하세요")
try:
video = coll.upload(url="https://example.com/video.mp4")
except InvalidRequestError as e:
print(f"업로드 실패: {e}")
| 시나리오 | 오류 메시지 | 해결 방법 |
|---|---|---|
| 이미 인덱싱된 비디오 인덱싱 | Spoken word index for video already exists | 이미 인덱싱된 경우 건너뛰려면 video.index_spoken_words(force=True) 사용 |
| 장면 인덱스가 이미 존재함 | Scene index with id XXXX already exists | re.search(r"id\s+([a-f0-9]+)", str(e))를 사용하여 오류에서 기존 scene_index_id 추출 |
| 검색 결과가 없음 | InvalidRequestError: No results found | 예외를 잡아서 빈 결과(shots = [])로 처리 |
| 리프레임 시간 초과 | 긴 비디오에서 무기한 차단됨 | start/end를 사용하여 구간을 제한하거나 비동기를 위해 callback_url 전달 |
| 타임라인의 음수 타임스탬프 | 암시적으로 손상된 스트림 생성 | VideoAsset을 생성하기 전에 항상 start >= 0 검증 |
generate_video() / create_collection() 실패 | Operation not allowed 또는 maximum limit | 플랜 제한 기능 — 사용자에게 플랜 제한에 대해 알림 |
녹화 세션 중 WebSocket 이벤트를 캡처하려면 ws_listener.py를 사용하세요. 데스크톱 캡처는 macOS만 지원합니다.
STATE_DIR="${VIDEODB_EVENTS_DIR:-$HOME/.local/state/videodb}"VIDEODB_EVENTS_DIR="$STATE_DIR" python scripts/ws_listener.py --clear "$STATE_DIR" &cat "$STATE_DIR/videodb_ws_id"$STATE_DIR/videodb_events.jsonl새로운 캡처를 시작할 때마다 --clear를 사용하여 이전 세션의 트랜스크립트와 시각적 이벤트가 새 세션으로 유출되지 않도록 하세요.
import json
import os
import time
from pathlib import Path
events_dir = Path(os.environ.get("VIDEODB_EVENTS_DIR", Path.home() / ".local" / "state" / "videodb"))
events_file = events_dir / "videodb_events.jsonl"
events = []
if events_file.exists():
with events_file.open(encoding="utf-8") as handle:
for line in handle:
try:
events.append(json.loads(line))
except json.JSONDecodeError:
continue
transcripts = [e["data"]["text"] for e in events if e.get("channel") == "transcript"]
cutoff = time.time() - 300
recent_visual = [
e for e in events
if e.get("channel") == "visual_index" and e["unix_ts"] > cutoff
]
참조 문서는 이 SKILL.md 파일과 인접한 reference/ 디렉터리에 있습니다. 필요한 경우 Glob 도구를 사용하여 찾으세요.
VideoDB가 작업을 지원하는 경우 ffmpeg, moviepy 또는 로컬 인코딩 도구를 사용하지 마세요. 트리밍, 클립 결합, 오디오 또는 음악 오버레이, 자막 추가, 텍스트/이미지 오버레이, 트랜스코딩, 해상도 변경, 가로세로비 변환, 플랫폼 요구 사항에 따른 크기 조정, 트랜스크립션 및 미디어 생성은 모두 VideoDB에 의해 서버 측에서 처리됩니다. reference/editor.md의 제한 사항에 나열된 작업(전환, 속도 변경, 자르기/확대, 색 보정, 볼륨 믹싱)에 대해서만 로컬 도구를 사용하세요.
| 문제 | VideoDB 해결 방법 |
|---|---|
| 플랫폼에서 비디오 가로세로비 또는 해상도 거부 | video.reframe() 또는 VideoConfig를 사용한 conn.transcode() |
| Twitter/Instagram/TikTok용 비디오 크기 조정 필요 | video.reframe(target="vertical") 또는 target="square" |
| 해상도 변경 필요 (예: 1080p → 720p) | VideoConfig(resolution=720)를 사용한 conn.transcode() |
| 비디오에 오디오/음악 오버레이 필요 | Timeline의 AudioAsset |
| 자막 추가 필요 | video.add_subtitle() 또는 CaptionAsset |
| 클립 결합/트리밍 필요 | Timeline의 VideoAsset |
| 음성 해설, 음악 또는 SFX 생성 필요 | coll.generate_voice(), generate_music(), generate_sound_effect() |
이 스킬의 참조 자료는 skills/videodb/reference/ 아래에 로컬로 제공됩니다. 실행 시 외부 저장소 링크를 따라가는 대신 위의 로컬 복사본을 사용하세요.