Skip to content

API

init module.

setup_django

setup_django() -> None

Setup the database.

Source code in src/video_vault/core/db/setup.py
def setup_django() -> None:
    """Setup the database."""
    if settings.configured:
        return

    # can be None in frozen apps and django needs it to be writable
    if sys.stdout is None:
        sys.stdout = StringIO()
    if sys.stderr is None:
        sys.stderr = StringIO()

    root_dir = Path(user_data_dir(APP_NAME, AUTHOR, ensure_exists=True))
    media_root = root_dir / "media"
    media_root.mkdir(parents=True, exist_ok=True)

    db_path = root_dir / "db" / "db.sqlite3"
    db_path.parent.mkdir(parents=True, exist_ok=True)

    settings.configure(
        DATABASES={
            "default": {
                "ENGINE": "django.db.backends.sqlite3",
                "NAME": str(db_path),
            },
        },
        INSTALLED_APPS=[
            db.__name__,
        ],
        MEDIA_ROOT=media_root,
        MEDIA_URL="/media/",
        SECRET_KEY=get_app_key_as_str(),
    )

    django.setup()

    call_command("migrate")

    logger.info("Django setup complete")

core

src package.

core

init module.

consts

This module contains constants for the application.

downloads

Downloads module.

This module contains functions to add downloads.

DownloadWorker
DownloadWorker(url: str, cookies: list[Cookie])

Bases: QThread

Worker to download a video.

Source code in src/video_vault/core/core/downloads.py
def __init__(self, url: str, cookies: list[Cookie]) -> None:
    """Initialize the worker."""
    super().__init__()
    self.ALL_WORKERS.append(self)  # must be inplace
    self.url = url
    self.cookies = cookies
    self.finished.connect(self.on_finished)
on_finished
on_finished() -> None

Handle the result of the download.

Source code in src/video_vault/core/core/downloads.py
def on_finished(self) -> None:
    """Handle the result of the download."""
    self.show_notification()
    self.ALL_WORKERS.remove(self)
    self.update_downloads_page()
run
run() -> None

Run the worker.

Source code in src/video_vault/core/core/downloads.py
def run(self) -> None:
    """Run the worker."""
    try:
        self.file = add_download(self.url, self.cookies)
        self.name = self.file.display_name
        self.successful = True
        self.error = None
    except DownloadError as e:
        self.name = self.url
        self.successful = False
        self.error = e
show_notification
show_notification() -> None

Show a popup with the result of the download.

Source code in src/video_vault/core/core/downloads.py
def show_notification(self) -> None:
    """Show a popup with the result of the download."""
    notification = Notification(
        title=(
            f"Download {'succeeded' if self.successful else 'failed'}: {self.name}"
        ),
        text=f"Error: {self.error}",
    )
    notification.show()
update_downloads_page
update_downloads_page() -> None

Update the downloads page.

Source code in src/video_vault/core/core/downloads.py
def update_downloads_page(self) -> None:
    """Update the downloads page."""
    from video_vault.core.ui.pages.downloads import (  # noqa: PLC0415
        Downloads as DownloadsPage,  # avoid circular import
    )

    if not self.successful:
        return

    downloads_page = DownloadsPage.get_page_static(DownloadsPage)
    downloads_page.add_download_button(self.file)
add_download
add_download(url: str, cookies: list[Cookie]) -> File

Add a download.

Source code in src/video_vault/core/core/downloads.py
def add_download(url: str, cookies: list[Cookie]) -> File:
    """Add a download."""
    with tempfile.TemporaryDirectory() as tempdir:
        path = do_download(tempdir, url, cookies)
        return save_download(path)
do_download
do_download(
    tempdir: str, url: str, cookies: list[Cookie]
) -> Path

Add a download.

Source code in src/video_vault/core/core/downloads.py
def do_download(tempdir: str, url: str, cookies: list[Cookie]) -> Path:
    """Add a download."""
    logger.info("Adding download: %s", url)

    # make ydl options so we don not write to file
    ffmpeg_path = get_ffmpeg_path()
    ydl_opts = {
        "paths": {"home": tempdir},
        "cookies": cookies,
        "ffmpeg_location": str(ffmpeg_path) if ffmpeg_path is not None else None,
        "format": "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]",
        "merge_output_format": "mp4",
        "postprocessors": [
            {
                "key": "FFmpegVideoConvertor",
                "preferedformat": "mp4",
            },
        ],
    }
    try:
        with yt_dlp.YoutubeDL(ydl_opts) as ydl:  # ty:ignore[invalid-argument-type]
            info = ydl.extract_info(url, download=True)
    except Exception as e:
        msg = f"Download failed: {e}"
        raise DownloadError(msg) from e

    return Path(ydl.prepare_filename(info))
save_download
save_download(path: Path) -> File

Save a download encrypted to disk.

Source code in src/video_vault/core/core/downloads.py
def save_download(path: Path) -> File:
    """Save a download encrypted to disk."""
    return File.create_encrypted(path)

ffmpeg

module to interact with ffmpeg.

get_ffmpeg_path
get_ffmpeg_path() -> Path | None

Get the path to ffmpeg.

Source code in src/video_vault/core/core/ffmpeg.py
def get_ffmpeg_path() -> Path | None:
    """Get the path to ffmpeg."""
    path = Path(imageio_ffmpeg.get_ffmpeg_exe())
    logger.info("Found ffmpeg at %s", path)
    return path

security

Security module.

This module contains functions to encrypt and decrypt data.

get_app_key_as_str
get_app_key_as_str() -> str

Get the key as a string.

Source code in src/video_vault/core/core/security.py
def get_app_key_as_str() -> str:
    """Get the key as a string."""
    get_or_create_aes_gcm(APP_NAME, AUTHOR)
    key = get_key_as_str(APP_NAME, AUTHOR, key_class=AESGCM)
    if key is None:
        msg = "Key not found"
        raise ValueError(msg)
    return key
get_or_create_app_aes_gcm
get_or_create_app_aes_gcm() -> AESGCM

Get the app secret using keyring.

If it does not exist, create it with a AESGCM.

Source code in src/video_vault/core/core/security.py
def get_or_create_app_aes_gcm() -> AESGCM:
    """Get the app secret using keyring.

    If it does not exist, create it with a AESGCM.
    """
    return get_or_create_aes_gcm(APP_NAME, AUTHOR)[0]

db

init module.

make_migrations

Script to create migrations for the video_vault.db app.

migrations

init module.

models

Models for the database.

File

Bases: BaseModel

File model.

display_name property
display_name: str

Get the display name.

create_encrypted classmethod
create_encrypted(path: Path, **kwargs: Any) -> File

Create a file.

Source code in src/video_vault/core/db/models.py
@classmethod
def create_encrypted(cls, path: Path, **kwargs: Any) -> "File":  # noqa: ANN401
    """Create a file."""
    aes_gcm = get_or_create_app_aes_gcm()

    decrypted_data = path.read_bytes()

    encrypted_data = EncryptedPyQFile.encrypt_data_static(decrypted_data, aes_gcm)

    return cls.objects.create(file=ContentFile(encrypted_data, path.name), **kwargs)
delete_file
delete_file(
    *args: Any, **kwargs: Any
) -> tuple[int, dict[str, int]]

Delete a file.

Source code in src/video_vault/core/db/models.py
def delete_file(self, *args: Any, **kwargs: Any) -> tuple[int, dict[str, int]]:  # noqa: ANN401
    """Delete a file."""
    # delete the file from the filesystem
    self.file.delete(save=False)
    # delete the file from the database
    return self.delete(*args, **kwargs)

setup

Database setup module.

This module contains the database settings. In the init we setup django settings and create the db if not existent.

setup_django
setup_django() -> None

Setup the database.

Source code in src/video_vault/core/db/setup.py
def setup_django() -> None:
    """Setup the database."""
    if settings.configured:
        return

    # can be None in frozen apps and django needs it to be writable
    if sys.stdout is None:
        sys.stdout = StringIO()
    if sys.stderr is None:
        sys.stderr = StringIO()

    root_dir = Path(user_data_dir(APP_NAME, AUTHOR, ensure_exists=True))
    media_root = root_dir / "media"
    media_root.mkdir(parents=True, exist_ok=True)

    db_path = root_dir / "db" / "db.sqlite3"
    db_path.parent.mkdir(parents=True, exist_ok=True)

    settings.configure(
        DATABASES={
            "default": {
                "ENGINE": "django.db.backends.sqlite3",
                "NAME": str(db_path),
            },
        },
        INSTALLED_APPS=[
            db.__name__,
        ],
        MEDIA_ROOT=media_root,
        MEDIA_URL="/media/",
        SECRET_KEY=get_app_key_as_str(),
    )

    django.setup()

    call_command("migrate")

    logger.info("Django setup complete")

ui

init module.

pages

init module.

add_downloads

Add downloads page module.

This module contains the add downloads page class for the VideoVault application.

AddDownloads

Bases: Browser

Add downloads page for the VideoVault application.

add_download_button
add_download_button() -> None

Add a download button.

Source code in src/video_vault/core/ui/pages/add_downloads.py
def add_download_button(self) -> None:
    """Add a download button."""
    download_arrow_icon = self.get_svg_icon("download_arrow")
    button = QPushButton(self.get_display_name().removesuffix("s"))
    button.setIcon(download_arrow_icon)
    # we need the button to be small
    button.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
    # connect to add download
    button.clicked.connect(self.on_add_download)
    # add button to layout
    self.h_layout.addWidget(button)
    # align the button to the right
    self.h_layout.setAlignment(
        button,
        Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop,
    )
on_add_download
on_add_download() -> None

Add a download.

Source code in src/video_vault/core/ui/pages/add_downloads.py
def on_add_download(self) -> None:
    """Add a download."""
    url = self.browser.url()
    domain = url.host()
    http_cookies = self.browser.get_domain_http_cookies(domain)
    worker = DownloadWorker(url=url.toString(), cookies=http_cookies)
    worker.start()
post_setup
post_setup() -> None

Setup the UI.

Source code in src/video_vault/core/ui/pages/add_downloads.py
def post_setup(self) -> None:
    """Setup the UI."""
pre_setup
pre_setup() -> None

Setup the UI.

Source code in src/video_vault/core/ui/pages/add_downloads.py
def pre_setup(self) -> None:
    """Setup the UI."""
    # add a download button in the top right
    self.add_download_button()
downloads

Downloads page module.

This module contains the downloads page class for the VideoVault application.

Downloads

Bases: Base

Downloads page for the VideoVault application.

add_add_downloads_button
add_add_downloads_button() -> None

Add a button to add a download.

Source code in src/video_vault/core/ui/pages/downloads.py
def add_add_downloads_button(self) -> None:
    """Add a button to add a download."""
    # now make the button top right in the layout, QV doesn't support this
    # so add a horizontal layout to the top row
    button = self.add_to_page_button(
        to_page_cls=AddDownloadsPage,
        layout=self.h_layout,
    )
    button.setIcon(self.get_svg_icon("plus_icon"))
    # we need the button to be small
    button.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
    # align the button to the right
    self.h_layout.setAlignment(
        button,
        Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop,
    )
add_delete_all_downloads_button
add_delete_all_downloads_button() -> None

Add a button to delete all downloads.

Source code in src/video_vault/core/ui/pages/downloads.py
def add_delete_all_downloads_button(self) -> None:
    """Add a button to delete all downloads."""
    button = QPushButton("Delete All")
    button.clicked.connect(self.on_delete_all_downloads)
    self.h_layout.addWidget(button)
    # align to be in center
    self.h_layout.setAlignment(
        button,
        Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignTop,
    )
    # icon
    button.setIcon(self.get_svg_icon("delete_garbage_can"))
    # we need the button to be small
    button.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
add_download_button
add_download_button(download: File) -> None

Add a download to the list.

Source code in src/video_vault/core/ui/pages/downloads.py
def add_download_button(self, download: File) -> None:
    """Add a download to the list."""
    # check if display name
    button = QPushButton(download.display_name)
    self.button_to_download[button] = download
    self.downloads_layout.addWidget(button)
    # give the button a QMenu with play and delete
    menu = QMenu(button)

    play_action = menu.addAction("Play")
    play_action.setIcon(self.get_svg_icon("play_icon"))
    play_action.triggered.connect(partial(self.play_download, download))

    delete_action = menu.addAction("Delete")
    delete_action.setIcon(self.get_svg_icon("delete_garbage_can"))
    delete_action.triggered.connect(
        partial(self.remove_download_and_button, button),
    )

    button.setMenu(menu)
add_download_buttons_scroll_area
add_download_buttons_scroll_area() -> None

Add a list of downloads to scroll through and on click the video plays.

Source code in src/video_vault/core/ui/pages/downloads.py
def add_download_buttons_scroll_area(self) -> None:
    """Add a list of downloads to scroll through and on click the video plays."""
    self.downloads = File.objects.all().order_by("-created_at")

    self.downloads_widget = QWidget()
    self.downloads_layout = QVBoxLayout(self.downloads_widget)
    self.downloads_layout.setSpacing(10)
    self.downloads_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)

    # for each download add a button with the name of the download
    self.button_to_download: dict[QPushButton, File] = {}
    for download in self.downloads:
        self.add_download_button(download)

    # Scroll area setup
    scroll_area = QScrollArea()
    scroll_area.setWidgetResizable(True)
    scroll_area.setWidget(self.downloads_widget)

    # Add the scroll area to the main layout
    self.v_layout.addWidget(scroll_area)
on_delete_all_downloads
on_delete_all_downloads() -> None

Delete all downloads.

Source code in src/video_vault/core/ui/pages/downloads.py
def on_delete_all_downloads(self) -> None:
    """Delete all downloads."""
    download_to_button = reverse_dict(self.button_to_download)
    for download in File.objects.all():
        button = download_to_button[download]
        self.remove_download_and_button(button)
play_download
play_download(download: File) -> None

Play the video.

Source code in src/video_vault/core/ui/pages/downloads.py
def play_download(self, download: File) -> None:
    """Play the video."""
    download.refresh_from_db()

    player_page = self.get_page(PlayerPage)

    # if already a video is playing then save its position
    if player_page.current_file is not None:
        player_page.current_file.last_position = player_page.media_player.position()
        player_page.current_file.save()
    player_page.current_file = download

    player_page.start_playback(Path(download.file.path), download.last_position)
post_setup
post_setup() -> None

Setup the UI.

Source code in src/video_vault/core/ui/pages/downloads.py
def post_setup(self) -> None:
    """Setup the UI."""
pre_setup
pre_setup() -> None

Setup the UI.

Source code in src/video_vault/core/ui/pages/downloads.py
def pre_setup(self) -> None:
    """Setup the UI."""
remove_download_and_button
remove_download_and_button(
    download_button: QPushButton,
) -> None

Remove a download from the list.

Source code in src/video_vault/core/ui/pages/downloads.py
def remove_download_and_button(self, download_button: QPushButton) -> None:
    """Remove a download from the list."""
    # stop the player if the current file is the one being deleted

    file = self.button_to_download[download_button]
    player_page = self.get_page(PlayerPage)
    if player_page.current_file == file:
        player_page.stop_playback()

    file.delete_file()
    self.downloads_layout.removeWidget(download_button)
    download_button.deleteLater()
    del self.button_to_download[download_button]
setup
setup() -> None

Setup the UI.

Source code in src/video_vault/core/ui/pages/downloads.py
def setup(self) -> None:
    """Setup the UI."""
    # add button in the top right to add a download
    self.add_delete_all_downloads_button()
    self.add_add_downloads_button()
    self.add_download_buttons_scroll_area()
player

Player page module.

This module contains the player page class for the VideoVault application.

Player

Bases: Player

Player page for the VideoVault application.

play_download
play_download(download: File) -> None

Play the video.

Source code in src/video_vault/core/ui/pages/player.py
def play_download(self, download: File) -> None:
    """Play the video."""
    self.stop_playback()
    download.refresh_from_db()
    self.current_file = download
    self.start_playback(Path(download.file.path), download.last_position)
post_setup
post_setup() -> None

Setup the UI.

Source code in src/video_vault/core/ui/pages/player.py
def post_setup(self) -> None:
    """Setup the UI."""
    self.current_file: File | None = None
pre_setup
pre_setup() -> None

Setup the UI.

Source code in src/video_vault/core/ui/pages/player.py
def pre_setup(self) -> None:
    """Setup the UI."""
start_playback
start_playback(path: Path, position: int = 0) -> None

Start playback.

Source code in src/video_vault/core/ui/pages/player.py
def start_playback(self, path: Path, position: int = 0) -> None:
    """Start playback."""
    aes_gcm = get_or_create_app_aes_gcm()
    self.play_encrypted_file(path, aes_gcm, position)
stop_playback
stop_playback() -> None

Stop playback.

Source code in src/video_vault/core/ui/pages/player.py
def stop_playback(self) -> None:
    """Stop playback."""
    if self.current_file is None:
        return
    self.current_file.last_position = self.media_player.position()
    self.current_file.save()
    self.current_file = None
    self.media_player.stop_and_close_io_device()

stylesheet

Contains the stylesheet for the application.

windows

init module.

main

Main window module.

This module contains the main window class for the VideoVault application.

VideoVault

Bases: Base

Main window for the VideoVault application.

get_all_page_classes classmethod
get_all_page_classes() -> list[type[Base]]

Get all page classes.

Source code in src/video_vault/core/ui/windows/main.py
@classmethod
def get_all_page_classes(cls) -> list[type[BasePage]]:
    """Get all page classes."""
    return BasePage.get_subclasses(package=pages)
get_start_page_cls classmethod
get_start_page_cls() -> type[Downloads]

Get the start page class.

Source code in src/video_vault/core/ui/windows/main.py
@classmethod
def get_start_page_cls(cls) -> type[DownloadsPage]:
    """Get the start page class."""
    return DownloadsPage
post_setup
post_setup() -> None

Setup the UI.

Source code in src/video_vault/core/ui/windows/main.py
def post_setup(self) -> None:
    """Setup the UI."""
pre_setup
pre_setup() -> None

Setup the UI.

Source code in src/video_vault/core/ui/windows/main.py
def pre_setup(self) -> None:
    """Setup the UI."""
    # set the play icon from winiutils.core
    play_icon = self.get_svg_icon("play_icon")
    self.setWindowIcon(play_icon)
setup
setup() -> None

Setup the UI.

Source code in src/video_vault/core/ui/windows/main.py
def setup(self) -> None:
    """Setup the UI."""

main

Main entrypoint for the project.

main

main() -> None

Main entrypoint for the project.

Source code in src/video_vault/main.py
def main() -> None:
    """Main entrypoint for the project."""
    run()

run

run() -> None

Main function to run the application.

Source code in src/video_vault/main.py
def run() -> None:
    """Main function to run the application."""
    # if pytest is running exit with 0 before creating the window
    # to avoid segfaults in headless environments
    if os.getenv("PYTEST_VERSION") is not None:
        return

    # Create QApplication - this manages the entire app
    app = QApplication(sys.argv)

    # set global style sheet
    app.setStyleSheet(STYLESHEET)

    # Create and show the main window
    window = VideoVaultWindow()

    window.showMaximized()
    # Start the event loop (keeps the app running)
    # This will block until the user closes the window
    logger.info("Starting event loop")
    app.exec()

rig

init module.

cli

init module.

subcommands

Subcommands for the CLI.

They will be automatically imported and added to the CLI IMPORTANT: All funcs in this file will be added as subcommands. So best to define the logic elsewhere and just call it here in a wrapper.

run
run() -> None

Run the video-vault app.

Source code in src/video_vault/rig/cli/subcommands.py
def run() -> None:
    """Run the video-vault app."""
    main()

configs

Package initialization.

remote_version_control

Package initialization.

workflows

Package initialization.

release

Configs for pyrig.

All subclasses of ConfigFile in the configs package are automatically called.

ReleaseWorkflowConfigFile

Bases: ReleaseWorkflowConfigFile, ReleaseWorkflowConfigFile

You can override methods from the base class to customize behavior.

resource_modules
resource_modules() -> Iterable[ModuleType]

Additional resources.

Source code in src/video_vault/rig/configs/remote_version_control/workflows/release.py
def resource_modules(self) -> Iterable[ModuleType]:
    """Additional resources."""
    return (*super().resource_modules(), resources, migrations)

resources

init module.

tools

Tool wrappers for CLI tools used in development workflows.

Tools are subclasses of Tool providing methods that return Args objects for type-safe command construction and execution.

tools

Override pyrig tools.

ProjectTester

Bases: ProjectTester, ProjectTester

ProjectTester class for video_vault.

threshold
threshold() -> int

Override the threshold method to set a custom coverage threshold.

Source code in src/video_vault/rig/tools/tools.py
def threshold(self) -> int:
    """Override the threshold method to set a custom coverage threshold."""
    return 50