Files
Owlbot/owlbot/builtin_modules/clips/__init__.py
T
LogalDeveloper 0ff3c7a6b4
CI / Formatting (push) Successful in 5s
CI / Linting (push) Successful in 5s
CI / Tests (Python 3.12) (push) Successful in 14s
CI / Tests (Python 3.13) (push) Successful in 14s
CI / Tests (Python 3.14) (push) Successful in 11s
CI / Type Checking (push) Successful in 10s
CI / Spelling (push) Successful in 5s
Enabled all Ruff lint rules and resolved findings with justified inline suppressions.
2026-04-13 15:31:06 -04:00

125 lines
3.5 KiB
Python

# Copyright 2026 Logan Fick
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Clips module for Owlbot.
Allows chat users to create clips from the live Owncast stream. Caches the HLS
stream in real time and provides a browser-based clip editor.
"""
from __future__ import annotations
import asyncio
import shutil
from pathlib import Path
from owlbot.api import ModuleContext, on_setup, on_teardown
from .commands import clip_command, clips_command, delclip_command
from .events import on_stream_started, on_stream_stopped
from .manager import ClipManager, get_manager
from .processing import VideoProcessor
from .repository import ClipRepository
from .routes import (
clip_page,
clip_thumbnail,
clip_video,
clips_list_page,
editor_js,
editor_page,
editor_preview_video,
editor_submit,
)
__all__ = [
"clip_command",
"clip_page",
"clip_thumbnail",
"clip_video",
"clips_command",
"clips_list_page",
"delclip_command",
"editor_js",
"editor_page",
"editor_preview_video",
"editor_submit",
"on_stream_started",
"on_stream_stopped",
"setup",
"teardown",
]
@on_setup
async def setup(ctx: ModuleContext) -> None:
"""Initialize the clips module.
Validates ffmpeg availability, creates the database schema, registers
config defaults, creates the clips storage directory, and starts caching
if a stream is already live.
:param ctx: Module context with config, storage, and other services.
"""
for binary in ("ffmpeg", "ffprobe"):
if shutil.which(binary) is None:
msg = (
f"'{binary}' not found in PATH. "
f"The clips module requires ffmpeg to be installed."
)
raise RuntimeError(msg)
ctx.config.register_defaults(
{
"cache_duration": 300,
"min_clip_length": 5,
"max_clip_length": 120,
"clips_dir": "data/clips",
"session_expiry": 900,
"grace_period": 120,
}
)
repo = ClipRepository(ctx.storage)
await repo.setup()
processor = VideoProcessor(ctx.logger)
clips_dir = Path(str(ctx.config.get("clips_dir")))
await asyncio.to_thread(clips_dir.mkdir, parents=True, exist_ok=True)
manager = ClipManager(ctx, repo, processor, clips_dir)
ctx.state["manager"] = manager
# Check if stream is already live.
try:
status = await ctx.owncast_client.get_status()
if status.get("online", False):
ctx.logger.info("Stream is already live. Starting HLS cache.")
await manager.start_caching()
except Exception: # noqa: BLE001 # best-effort; non-fatal if Owncast is unreachable during setup
ctx.logger.warning(
"Could not check Owncast status during setup.", exc_info=True
)
@on_teardown
async def teardown(ctx: ModuleContext) -> None:
"""Clean up the clips module.
:param ctx: Module context.
"""
manager = get_manager(ctx)
await manager.teardown()
ctx.state["manager"] = None