7685 lines
356 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Build Power. Not Rent It. Own your digital infrastructure.">
<meta name="author" content="Bunker Operations">
<link rel="canonical" href="https://bnkserve.org/v2/features/media/video-library/">
<link rel="prev" href="../">
<link rel="next" href="../upload/">
<link rel="icon" href="../../../../assets/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.1">
<title>Video Library - Changemaker Lite</title>
<link rel="stylesheet" href="../../../../assets/stylesheets/main.484c7ddc.min.css">
<link rel="stylesheet" href="../../../../assets/stylesheets/palette.ab4e12ef.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Inter";--md-code-font:"JetBrains Mono"}</style>
<link rel="stylesheet" href="../../../../stylesheets/extra.css">
<link rel="stylesheet" href="../../../../stylesheets/home.css">
<link rel="stylesheet" href="../../../../assets/css/video-player.css">
<script>__md_scope=new URL("../../../..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
<meta property="og:type" content="website" />
<meta property="og:title" content="Video Library - Changemaker Lite" />
<meta property="og:description" content="Build Power. Not Rent It. Own your digital infrastructure." />
<meta property="og:image" content="https://bnkserve.org/assets/images/social/v2/features/media/video-library.png" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:url" content="https://bnkserve.org/v2/features/media/video-library/" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:title" content="Video Library - Changemaker Lite" />
<meta property="twitter:description" content="Build Power. Not Rent It. Own your digital infrastructure." />
<meta property="twitter:image" content="https://bnkserve.org/assets/images/social/v2/features/media/video-library.png" />
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="amber">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#video-library-management" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow md-header--lifted" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../../../.." title="Changemaker Lite" class="md-header__button md-logo" aria-label="Changemaker Lite" data-md-component="logo">
<img src="../../../../assets/logo.png" alt="logo">
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Changemaker Lite
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Video Library
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="amber" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_0">
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_1" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m17.75 4.09-2.53 1.94.91 3.06-2.63-1.81-2.63 1.81.91-3.06-2.53-1.94L12.44 4l1.06-3 1.06 3zm3.5 6.91-1.64 1.25.59 1.98-1.7-1.17-1.7 1.17.59-1.98L15.75 11l2.06-.05L18.5 9l.69 1.95zm-2.28 4.95c.83-.08 1.72 1.1 1.19 1.85-.32.45-.66.87-1.08 1.27C15.17 23 8.84 23 4.94 19.07c-3.91-3.9-3.91-10.24 0-14.14.4-.4.82-.76 1.27-1.08.75-.53 1.93.36 1.85 1.19-.27 2.86.69 5.83 2.89 8.02a9.96 9.96 0 0 0 8.02 2.89m-1.64 2.02a12.08 12.08 0 0 1-7.8-3.47c-2.17-2.19-3.33-5-3.49-7.82-2.81 3.14-2.7 7.96.31 10.98 3.02 3.01 7.84 3.12 10.98.31"/></svg>
</label>
<input class="md-option" data-md-color-media="" data-md-color-scheme="default" data-md-color-primary="deep-purple" data-md-color-accent="amber" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_1">
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_0" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 7a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5m0 2a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3m0-7 2.39 3.42C13.65 5.15 12.84 5 12 5s-1.65.15-2.39.42zM3.34 7l4.16-.35A7.2 7.2 0 0 0 5.94 8.5c-.44.74-.69 1.5-.83 2.29zm.02 10 1.76-3.77a7.131 7.131 0 0 0 2.38 4.14zM20.65 7l-1.77 3.79a7.02 7.02 0 0 0-2.38-4.15zm-.01 10-4.14.36c.59-.51 1.12-1.14 1.54-1.86.42-.73.69-1.5.83-2.29zM12 22l-2.41-3.44c.74.27 1.55.44 2.41.44.82 0 1.63-.17 2.37-.44z"/></svg>
</label>
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://gitea.bnkops.com/admin/changemaker.lite" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
changemaker.lite
</div>
</a>
</div>
</nav>
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item">
<a href="../../../.." class="md-tabs__link">
Home
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="../../../" class="md-tabs__link">
V2 Documentation
</a>
</li>
<li class="md-tabs__item">
<a href="../../../../phil/" class="md-tabs__link">
Philosophy
</a>
</li>
<li class="md-tabs__item">
<a href="../../../../v1/" class="md-tabs__link">
V1 Documentation (Legacy)
</a>
</li>
<li class="md-tabs__item">
<a href="../../../../blog/" class="md-tabs__link">
Blog
</a>
</li>
</ul>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../../../.." title="Changemaker Lite" class="md-nav__button md-logo" aria-label="Changemaker Lite" data-md-component="logo">
<img src="../../../../assets/logo.png" alt="logo">
</a>
Changemaker Lite
</label>
<div class="md-nav__source">
<a href="https://gitea.bnkops.com/admin/changemaker.lite" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
changemaker.lite
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../.." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2" checked>
<div class="md-nav__link md-nav__container">
<a href="../../../" class="md-nav__link ">
<span class="md-ellipsis">
V2 Documentation
</span>
</a>
<label class="md-nav__link " for="__nav_2" id="__nav_2_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_2_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_2">
<span class="md-nav__icon md-icon"></span>
V2 Documentation
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_2" >
<div class="md-nav__link md-nav__container">
<a href="../../../getting-started/" class="md-nav__link ">
<span class="md-ellipsis">
Getting Started
</span>
</a>
<label class="md-nav__link " for="__nav_2_2" id="__nav_2_2_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_2_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_2">
<span class="md-nav__icon md-icon"></span>
Getting Started
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../getting-started/quick-start/" class="md-nav__link">
<span class="md-ellipsis">
Quick Start
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_3" >
<div class="md-nav__link md-nav__container">
<a href="../../../architecture/" class="md-nav__link ">
<span class="md-ellipsis">
Architecture
</span>
</a>
<label class="md-nav__link " for="__nav_2_3" id="__nav_2_3_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_3_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_3">
<span class="md-nav__icon md-icon"></span>
Architecture
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../architecture/dual-api/" class="md-nav__link">
<span class="md-ellipsis">
Dual API System
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../architecture/authentication/" class="md-nav__link">
<span class="md-ellipsis">
Authentication & Security
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_4" >
<div class="md-nav__link md-nav__container">
<a href="../../../backend/" class="md-nav__link ">
<span class="md-ellipsis">
Backend
</span>
</a>
<label class="md-nav__link " for="__nav_2_4" id="__nav_2_4_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_4_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_4">
<span class="md-nav__icon md-icon"></span>
Backend
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../backend/modules/" class="md-nav__link">
<span class="md-ellipsis">
Modules
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../backend/services/" class="md-nav__link">
<span class="md-ellipsis">
Services
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../backend/middleware/" class="md-nav__link">
<span class="md-ellipsis">
Middleware
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../backend/utilities/" class="md-nav__link">
<span class="md-ellipsis">
Utilities
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_5" >
<div class="md-nav__link md-nav__container">
<a href="../../../frontend/" class="md-nav__link ">
<span class="md-ellipsis">
Frontend
</span>
</a>
<label class="md-nav__link " for="__nav_2_5" id="__nav_2_5_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_5_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_5">
<span class="md-nav__icon md-icon"></span>
Frontend
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../frontend/components/" class="md-nav__link">
<span class="md-ellipsis">
Components
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../frontend/layouts/" class="md-nav__link">
<span class="md-ellipsis">
Layouts
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../frontend/pages/" class="md-nav__link">
<span class="md-ellipsis">
Pages
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_6" >
<div class="md-nav__link md-nav__container">
<a href="../../../database/" class="md-nav__link ">
<span class="md-ellipsis">
Database
</span>
</a>
<label class="md-nav__link " for="__nav_2_6" id="__nav_2_6_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_6_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_6">
<span class="md-nav__icon md-icon"></span>
Database
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../database/schema/" class="md-nav__link">
<span class="md-ellipsis">
Schema Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../database/migrations/" class="md-nav__link">
<span class="md-ellipsis">
Migrations
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../database/seeding/" class="md-nav__link">
<span class="md-ellipsis">
Seeding
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../database/indexes/" class="md-nav__link">
<span class="md-ellipsis">
Indexes
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../database/models/" class="md-nav__link">
<span class="md-ellipsis">
Models
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2_7" checked>
<div class="md-nav__link md-nav__container">
<a href="../../" class="md-nav__link ">
<span class="md-ellipsis">
Features
</span>
</a>
<label class="md-nav__link " for="__nav_2_7" id="__nav_2_7_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_7_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_2_7">
<span class="md-nav__icon md-icon"></span>
Features
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../influence/" class="md-nav__link">
<span class="md-ellipsis">
Influence
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../map/" class="md-nav__link">
<span class="md-ellipsis">
Map
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../landing-pages/" class="md-nav__link">
<span class="md-ellipsis">
Landing Pages
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../email-templates/" class="md-nav__link">
<span class="md-ellipsis">
Email Templates
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2_7_6" checked>
<div class="md-nav__link md-nav__container">
<a href="../" class="md-nav__link ">
<span class="md-ellipsis">
Media
</span>
</a>
<label class="md-nav__link " for="__nav_2_7_6" id="__nav_2_7_6_label" tabindex="0">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="3" aria-labelledby="__nav_2_7_6_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_2_7_6">
<span class="md-nav__icon md-icon"></span>
Media
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Video Library
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Video Library
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="On this page">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
On this page
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#overview" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#architecture" class="md-nav__link">
<span class="md-ellipsis">
Architecture
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#database-model-drizzle" class="md-nav__link">
<span class="md-ellipsis">
Database Model (Drizzle)
</span>
</a>
<nav class="md-nav" aria-label="Database Model (Drizzle)">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#videos-table-schema" class="md-nav__link">
<span class="md-ellipsis">
Videos Table Schema
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#directory-types-enum" class="md-nav__link">
<span class="md-ellipsis">
Directory Types Enum
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#quality-classifications" class="md-nav__link">
<span class="md-ellipsis">
Quality Classifications
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#orientation-detection" class="md-nav__link">
<span class="md-ellipsis">
Orientation Detection
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#api-endpoints" class="md-nav__link">
<span class="md-ellipsis">
API Endpoints
</span>
</a>
<nav class="md-nav" aria-label="API Endpoints">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#list-videos" class="md-nav__link">
<span class="md-ellipsis">
List Videos
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#get-video-details" class="md-nav__link">
<span class="md-ellipsis">
Get Video Details
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#create-video-record" class="md-nav__link">
<span class="md-ellipsis">
Create Video Record
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#update-video-metadata" class="md-nav__link">
<span class="md-ellipsis">
Update Video Metadata
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#delete-video" class="md-nav__link">
<span class="md-ellipsis">
Delete Video
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#scan-directory" class="md-nav__link">
<span class="md-ellipsis">
Scan Directory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#validate-video" class="md-nav__link">
<span class="md-ellipsis">
Validate Video
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#configuration" class="md-nav__link">
<span class="md-ellipsis">
Configuration
</span>
</a>
<nav class="md-nav" aria-label="Configuration">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#environment-variables" class="md-nav__link">
<span class="md-ellipsis">
Environment Variables
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#docker-volume-mounts" class="md-nav__link">
<span class="md-ellipsis">
Docker Volume Mounts
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#site-settings" class="md-nav__link">
<span class="md-ellipsis">
Site Settings
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#admin-workflow" class="md-nav__link">
<span class="md-ellipsis">
Admin Workflow
</span>
</a>
<nav class="md-nav" aria-label="Admin Workflow">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#viewing-the-video-library" class="md-nav__link">
<span class="md-ellipsis">
Viewing the Video Library
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#scanning-a-directory" class="md-nav__link">
<span class="md-ellipsis">
Scanning a Directory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#editing-video-metadata" class="md-nav__link">
<span class="md-ellipsis">
Editing Video Metadata
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#validating-videos" class="md-nav__link">
<span class="md-ellipsis">
Validating Videos
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#deleting-videos" class="md-nav__link">
<span class="md-ellipsis">
Deleting Videos
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#directory-structure" class="md-nav__link">
<span class="md-ellipsis">
Directory Structure
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#code-examples" class="md-nav__link">
<span class="md-ellipsis">
Code Examples
</span>
</a>
<nav class="md-nav" aria-label="Code Examples">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#list-videos-with-filters-fastify-route" class="md-nav__link">
<span class="md-ellipsis">
List Videos with Filters (Fastify Route)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#scan-directory-for-videos" class="md-nav__link">
<span class="md-ellipsis">
Scan Directory for Videos
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#validate-video-metadata" class="md-nav__link">
<span class="md-ellipsis">
Validate Video Metadata
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#frontend-library-page-table" class="md-nav__link">
<span class="md-ellipsis">
Frontend: Library Page Table
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#troubleshooting" class="md-nav__link">
<span class="md-ellipsis">
Troubleshooting
</span>
</a>
<nav class="md-nav" aria-label="Troubleshooting">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#problem-media-api-not-accessible" class="md-nav__link">
<span class="md-ellipsis">
Problem: Media API Not Accessible
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-scan-finds-no-videos" class="md-nav__link">
<span class="md-ellipsis">
Problem: Scan Finds No Videos
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-ffprobe-validation-fails" class="md-nav__link">
<span class="md-ellipsis">
Problem: FFprobe Validation Fails
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-drizzle-schema-changes-not-applied" class="md-nav__link">
<span class="md-ellipsis">
Problem: Drizzle Schema Changes Not Applied
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-large-library-performance" class="md-nav__link">
<span class="md-ellipsis">
Problem: Large Library Performance
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#performance-considerations" class="md-nav__link">
<span class="md-ellipsis">
Performance Considerations
</span>
</a>
<nav class="md-nav" aria-label="Performance Considerations">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#directory-scans" class="md-nav__link">
<span class="md-ellipsis">
Directory Scans
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#ffprobe-extraction" class="md-nav__link">
<span class="md-ellipsis">
FFprobe Extraction
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#database-queries" class="md-nav__link">
<span class="md-ellipsis">
Database Queries
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#thumbnail-generation" class="md-nav__link">
<span class="md-ellipsis">
Thumbnail Generation
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#dual-api-architecture" class="md-nav__link">
<span class="md-ellipsis">
Dual API Architecture
</span>
</a>
<nav class="md-nav" aria-label="Dual API Architecture">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#why-separate-fastify-api" class="md-nav__link">
<span class="md-ellipsis">
Why Separate Fastify API?
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#database-sharing-strategy" class="md-nav__link">
<span class="md-ellipsis">
Database Sharing Strategy
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#migration-strategy-roadmap" class="md-nav__link">
<span class="md-ellipsis">
Migration Strategy Roadmap
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#related-documentation" class="md-nav__link">
<span class="md-ellipsis">
Related Documentation
</span>
</a>
<nav class="md-nav" aria-label="Related Documentation">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#backend-documentation" class="md-nav__link">
<span class="md-ellipsis">
Backend Documentation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#frontend-documentation" class="md-nav__link">
<span class="md-ellipsis">
Frontend Documentation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#database-documentation" class="md-nav__link">
<span class="md-ellipsis">
Database Documentation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#feature-documentation" class="md-nav__link">
<span class="md-ellipsis">
Feature Documentation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#integration-documentation" class="md-nav__link">
<span class="md-ellipsis">
Integration Documentation
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#next-steps" class="md-nav__link">
<span class="md-ellipsis">
Next Steps
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../upload/" class="md-nav__link">
<span class="md-ellipsis">
Upload System
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../public-gallery/" class="md-nav__link">
<span class="md-ellipsis">
Public Gallery
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../jobs/" class="md-nav__link">
<span class="md-ellipsis">
Jobs
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../newsletter/" class="md-nav__link">
<span class="md-ellipsis">
Newsletter
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../observability/" class="md-nav__link">
<span class="md-ellipsis">
Observability
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../tunnel/" class="md-nav__link">
<span class="md-ellipsis">
Tunnel
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_8" >
<div class="md-nav__link md-nav__container">
<a href="../../../deployment/" class="md-nav__link ">
<span class="md-ellipsis">
Deployment
</span>
</a>
<label class="md-nav__link " for="__nav_2_8" id="__nav_2_8_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_8_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_8">
<span class="md-nav__icon md-icon"></span>
Deployment
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../deployment/docker-compose/" class="md-nav__link">
<span class="md-ellipsis">
Docker Compose
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/environment-variables/" class="md-nav__link">
<span class="md-ellipsis">
Environment Variables
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/nginx/" class="md-nav__link">
<span class="md-ellipsis">
Nginx Configuration
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/ssl-tls/" class="md-nav__link">
<span class="md-ellipsis">
SSL/TLS
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/tunneling/" class="md-nav__link">
<span class="md-ellipsis">
Tunneling
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/monitoring-stack/" class="md-nav__link">
<span class="md-ellipsis">
Monitoring Stack
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/healthchecks/" class="md-nav__link">
<span class="md-ellipsis">
Health Checks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/scaling/" class="md-nav__link">
<span class="md-ellipsis">
Scaling
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/backup-restore/" class="md-nav__link">
<span class="md-ellipsis">
Backup & Restore
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_9" >
<div class="md-nav__link md-nav__container">
<a href="../../../development/" class="md-nav__link ">
<span class="md-ellipsis">
Development
</span>
</a>
<label class="md-nav__link " for="__nav_2_9" id="__nav_2_9_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_9_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_9">
<span class="md-nav__icon md-icon"></span>
Development
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../development/local-setup/" class="md-nav__link">
<span class="md-ellipsis">
Local Setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/docker-workflow/" class="md-nav__link">
<span class="md-ellipsis">
Docker Workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/git-workflow/" class="md-nav__link">
<span class="md-ellipsis">
Git Workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/npm-commands/" class="md-nav__link">
<span class="md-ellipsis">
NPM Commands
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/migrations/" class="md-nav__link">
<span class="md-ellipsis">
Migrations
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/typescript/" class="md-nav__link">
<span class="md-ellipsis">
TypeScript
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/testing/" class="md-nav__link">
<span class="md-ellipsis">
Testing
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/debugging/" class="md-nav__link">
<span class="md-ellipsis">
Debugging
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/code-style/" class="md-nav__link">
<span class="md-ellipsis">
Code Style
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_10" >
<div class="md-nav__link md-nav__container">
<a href="../../../api-reference/" class="md-nav__link ">
<span class="md-ellipsis">
API Reference
</span>
</a>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_10_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_10">
<span class="md-nav__icon md-icon"></span>
API Reference
</label>
<ul class="md-nav__list" data-md-scrollfix>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_11" >
<div class="md-nav__link md-nav__container">
<a href="../../../user-guides/" class="md-nav__link ">
<span class="md-ellipsis">
User Guides
</span>
</a>
<label class="md-nav__link " for="__nav_2_11" id="__nav_2_11_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_11_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_11">
<span class="md-nav__icon md-icon"></span>
User Guides
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../user-guides/admin-guide/" class="md-nav__link">
<span class="md-ellipsis">
Admin Guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../user-guides/campaign-manager-guide/" class="md-nav__link">
<span class="md-ellipsis">
Campaign Manager Guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../user-guides/map-organizer-guide/" class="md-nav__link">
<span class="md-ellipsis">
Map Organizer Guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../user-guides/content-editor-guide/" class="md-nav__link">
<span class="md-ellipsis">
Content Editor Guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../user-guides/volunteer-guide/" class="md-nav__link">
<span class="md-ellipsis">
Volunteer Guide
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_12" >
<div class="md-nav__link md-nav__container">
<a href="../../../troubleshooting/" class="md-nav__link ">
<span class="md-ellipsis">
Troubleshooting
</span>
</a>
<label class="md-nav__link " for="__nav_2_12" id="__nav_2_12_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_12_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_12">
<span class="md-nav__icon md-icon"></span>
Troubleshooting
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../troubleshooting/faq/" class="md-nav__link">
<span class="md-ellipsis">
FAQ
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/common-errors/" class="md-nav__link">
<span class="md-ellipsis">
Common Errors
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/auth-issues/" class="md-nav__link">
<span class="md-ellipsis">
Auth Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/database-issues/" class="md-nav__link">
<span class="md-ellipsis">
Database Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/docker-issues/" class="md-nav__link">
<span class="md-ellipsis">
Docker Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/email-issues/" class="md-nav__link">
<span class="md-ellipsis">
Email Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/geocoding-issues/" class="md-nav__link">
<span class="md-ellipsis">
Geocoding Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/monitoring-issues/" class="md-nav__link">
<span class="md-ellipsis">
Monitoring Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/performance-optimization/" class="md-nav__link">
<span class="md-ellipsis">
Performance Optimization
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_13" >
<div class="md-nav__link md-nav__container">
<a href="../../../migration/" class="md-nav__link ">
<span class="md-ellipsis">
Migration
</span>
</a>
<label class="md-nav__link " for="__nav_2_13" id="__nav_2_13_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_13_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_13">
<span class="md-nav__icon md-icon"></span>
Migration
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../migration/feature-parity/" class="md-nav__link">
<span class="md-ellipsis">
Feature Parity
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../migration/breaking-changes/" class="md-nav__link">
<span class="md-ellipsis">
Breaking Changes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../migration/api-changes/" class="md-nav__link">
<span class="md-ellipsis">
API Changes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../migration/data-migration/" class="md-nav__link">
<span class="md-ellipsis">
Data Migration
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_14" >
<div class="md-nav__link md-nav__container">
<a href="../../../contributing/" class="md-nav__link ">
<span class="md-ellipsis">
Contributing
</span>
</a>
<label class="md-nav__link " for="__nav_2_14" id="__nav_2_14_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_14_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_14">
<span class="md-nav__icon md-icon"></span>
Contributing
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../contributing/development-setup/" class="md-nav__link">
<span class="md-ellipsis">
Development Setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../contributing/code-of-conduct/" class="md-nav__link">
<span class="md-ellipsis">
Code of Conduct
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../contributing/pull-requests/" class="md-nav__link">
<span class="md-ellipsis">
Pull Requests
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../contributing/roadmap/" class="md-nav__link">
<span class="md-ellipsis">
Roadmap
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../../phil/" class="md-nav__link">
<span class="md-ellipsis">
Philosophy
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../../v1/" class="md-nav__link">
<span class="md-ellipsis">
V1 Documentation (Legacy)
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../../blog/" class="md-nav__link">
<span class="md-ellipsis">
Blog
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="On this page">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
On this page
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#overview" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#architecture" class="md-nav__link">
<span class="md-ellipsis">
Architecture
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#database-model-drizzle" class="md-nav__link">
<span class="md-ellipsis">
Database Model (Drizzle)
</span>
</a>
<nav class="md-nav" aria-label="Database Model (Drizzle)">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#videos-table-schema" class="md-nav__link">
<span class="md-ellipsis">
Videos Table Schema
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#directory-types-enum" class="md-nav__link">
<span class="md-ellipsis">
Directory Types Enum
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#quality-classifications" class="md-nav__link">
<span class="md-ellipsis">
Quality Classifications
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#orientation-detection" class="md-nav__link">
<span class="md-ellipsis">
Orientation Detection
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#api-endpoints" class="md-nav__link">
<span class="md-ellipsis">
API Endpoints
</span>
</a>
<nav class="md-nav" aria-label="API Endpoints">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#list-videos" class="md-nav__link">
<span class="md-ellipsis">
List Videos
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#get-video-details" class="md-nav__link">
<span class="md-ellipsis">
Get Video Details
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#create-video-record" class="md-nav__link">
<span class="md-ellipsis">
Create Video Record
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#update-video-metadata" class="md-nav__link">
<span class="md-ellipsis">
Update Video Metadata
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#delete-video" class="md-nav__link">
<span class="md-ellipsis">
Delete Video
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#scan-directory" class="md-nav__link">
<span class="md-ellipsis">
Scan Directory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#validate-video" class="md-nav__link">
<span class="md-ellipsis">
Validate Video
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#configuration" class="md-nav__link">
<span class="md-ellipsis">
Configuration
</span>
</a>
<nav class="md-nav" aria-label="Configuration">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#environment-variables" class="md-nav__link">
<span class="md-ellipsis">
Environment Variables
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#docker-volume-mounts" class="md-nav__link">
<span class="md-ellipsis">
Docker Volume Mounts
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#site-settings" class="md-nav__link">
<span class="md-ellipsis">
Site Settings
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#admin-workflow" class="md-nav__link">
<span class="md-ellipsis">
Admin Workflow
</span>
</a>
<nav class="md-nav" aria-label="Admin Workflow">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#viewing-the-video-library" class="md-nav__link">
<span class="md-ellipsis">
Viewing the Video Library
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#scanning-a-directory" class="md-nav__link">
<span class="md-ellipsis">
Scanning a Directory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#editing-video-metadata" class="md-nav__link">
<span class="md-ellipsis">
Editing Video Metadata
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#validating-videos" class="md-nav__link">
<span class="md-ellipsis">
Validating Videos
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#deleting-videos" class="md-nav__link">
<span class="md-ellipsis">
Deleting Videos
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#directory-structure" class="md-nav__link">
<span class="md-ellipsis">
Directory Structure
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#code-examples" class="md-nav__link">
<span class="md-ellipsis">
Code Examples
</span>
</a>
<nav class="md-nav" aria-label="Code Examples">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#list-videos-with-filters-fastify-route" class="md-nav__link">
<span class="md-ellipsis">
List Videos with Filters (Fastify Route)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#scan-directory-for-videos" class="md-nav__link">
<span class="md-ellipsis">
Scan Directory for Videos
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#validate-video-metadata" class="md-nav__link">
<span class="md-ellipsis">
Validate Video Metadata
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#frontend-library-page-table" class="md-nav__link">
<span class="md-ellipsis">
Frontend: Library Page Table
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#troubleshooting" class="md-nav__link">
<span class="md-ellipsis">
Troubleshooting
</span>
</a>
<nav class="md-nav" aria-label="Troubleshooting">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#problem-media-api-not-accessible" class="md-nav__link">
<span class="md-ellipsis">
Problem: Media API Not Accessible
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-scan-finds-no-videos" class="md-nav__link">
<span class="md-ellipsis">
Problem: Scan Finds No Videos
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-ffprobe-validation-fails" class="md-nav__link">
<span class="md-ellipsis">
Problem: FFprobe Validation Fails
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-drizzle-schema-changes-not-applied" class="md-nav__link">
<span class="md-ellipsis">
Problem: Drizzle Schema Changes Not Applied
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-large-library-performance" class="md-nav__link">
<span class="md-ellipsis">
Problem: Large Library Performance
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#performance-considerations" class="md-nav__link">
<span class="md-ellipsis">
Performance Considerations
</span>
</a>
<nav class="md-nav" aria-label="Performance Considerations">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#directory-scans" class="md-nav__link">
<span class="md-ellipsis">
Directory Scans
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#ffprobe-extraction" class="md-nav__link">
<span class="md-ellipsis">
FFprobe Extraction
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#database-queries" class="md-nav__link">
<span class="md-ellipsis">
Database Queries
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#thumbnail-generation" class="md-nav__link">
<span class="md-ellipsis">
Thumbnail Generation
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#dual-api-architecture" class="md-nav__link">
<span class="md-ellipsis">
Dual API Architecture
</span>
</a>
<nav class="md-nav" aria-label="Dual API Architecture">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#why-separate-fastify-api" class="md-nav__link">
<span class="md-ellipsis">
Why Separate Fastify API?
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#database-sharing-strategy" class="md-nav__link">
<span class="md-ellipsis">
Database Sharing Strategy
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#migration-strategy-roadmap" class="md-nav__link">
<span class="md-ellipsis">
Migration Strategy Roadmap
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#related-documentation" class="md-nav__link">
<span class="md-ellipsis">
Related Documentation
</span>
</a>
<nav class="md-nav" aria-label="Related Documentation">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#backend-documentation" class="md-nav__link">
<span class="md-ellipsis">
Backend Documentation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#frontend-documentation" class="md-nav__link">
<span class="md-ellipsis">
Frontend Documentation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#database-documentation" class="md-nav__link">
<span class="md-ellipsis">
Database Documentation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#feature-documentation" class="md-nav__link">
<span class="md-ellipsis">
Feature Documentation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#integration-documentation" class="md-nav__link">
<span class="md-ellipsis">
Integration Documentation
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#next-steps" class="md-nav__link">
<span class="md-ellipsis">
Next Steps
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<nav class="md-path" aria-label="Navigation" >
<ol class="md-path__list">
<li class="md-path__item">
<a href="../../../.." class="md-path__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-path__item">
<a href="../../../" class="md-path__link">
<span class="md-ellipsis">
V2 Documentation
</span>
</a>
</li>
<li class="md-path__item">
<a href="../../" class="md-path__link">
<span class="md-ellipsis">
Features
</span>
</a>
</li>
<li class="md-path__item">
<a href="../" class="md-path__link">
<span class="md-ellipsis">
Media
</span>
</a>
</li>
</ol>
</nav>
<article class="md-content__inner md-typeset">
<a href="https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/mkdocs/docs/v2/features/media/video-library.md" title="Edit this page" class="md-content__button md-icon" rel="edit">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10 20H6V4h7v5h5v3.1l2-2V8l-6-6H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h4zm10.2-7c.1 0 .3.1.4.2l1.3 1.3c.2.2.2.6 0 .8l-1 1-2.1-2.1 1-1c.1-.1.2-.2.4-.2m0 3.9L14.1 23H12v-2.1l6.1-6.1z"/></svg>
</a>
<a href="https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/mkdocs/docs/v2/features/media/video-library.md" title="View source of this page" class="md-content__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17 18c.56 0 1 .44 1 1s-.44 1-1 1-1-.44-1-1 .44-1 1-1m0-3c-2.73 0-5.06 1.66-6 4 .94 2.34 3.27 4 6 4s5.06-1.66 6-4c-.94-2.34-3.27-4-6-4m0 6.5a2.5 2.5 0 0 1-2.5-2.5 2.5 2.5 0 0 1 2.5-2.5 2.5 2.5 0 0 1 2.5 2.5 2.5 2.5 0 0 1-2.5 2.5M9.27 20H6V4h7v5h5v4.07c.7.08 1.36.25 2 .49V8l-6-6H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h4.5a8.2 8.2 0 0 1-1.23-2"/></svg>
</a>
<h1 id="video-library-management">Video Library Management<a class="headerlink" href="#video-library-management" title="Permanent link">&para;</a></h1>
<h2 id="overview">Overview<a class="headerlink" href="#overview" title="Permanent link">&para;</a></h2>
<p>The Video Library system provides comprehensive video asset management through a dedicated Fastify microservice running on port 4100, separate from the main Express API. This dual API architecture allows the media system to operate independently while sharing the same PostgreSQL database.</p>
<p><strong>Key Features:</strong></p>
<ul>
<li><strong>Dual API Architecture</strong> — Fastify media API (port 4100) separate from Express API (port 4000)</li>
<li><strong>Drizzle ORM</strong> — Media tables use Drizzle ORM instead of Prisma for schema flexibility</li>
<li><strong>9 Directory Types</strong> — Organized library structure (studios, gifs, private, inbox, curated, playback, compilations, videos, highlights)</li>
<li><strong>FFprobe Integration</strong> — Automatic metadata extraction (duration, dimensions, orientation, quality, audio detection)</li>
<li><strong>Video CRUD</strong> — Full create, read, update, delete operations (admin-only)</li>
<li><strong>Directory Scanning</strong> — Bulk import videos from filesystem with automatic record creation</li>
<li><strong>Validation System</strong> — Re-validate videos to refresh metadata and check file integrity</li>
<li><strong>File Hashing</strong> — Duplicate detection via SHA-256 file hashing</li>
<li><strong>Soft Delete</strong> — Videos marked invalid instead of hard deletion (preserves history)</li>
<li><strong>Thumbnail Support</strong> — Custom thumbnail paths for video previews</li>
</ul>
<p><strong>Access Control:</strong></p>
<ul>
<li>All video library operations require <code>SUPER_ADMIN</code> role</li>
<li>Public video viewing handled separately via Shared Media system (see <code>public-gallery.md</code>)</li>
</ul>
<p><strong>Technology Stack:</strong></p>
<ul>
<li><strong>Fastify 4.x</strong> — High-performance Node.js web framework</li>
<li><strong>Drizzle ORM</strong> — TypeScript-first ORM with zero-runtime overhead</li>
<li><strong>FFprobe</strong> — FFmpeg's media file analyzer for metadata extraction</li>
<li><strong>PostgreSQL 16</strong> — Shared database with main API</li>
</ul>
<hr />
<h2 id="architecture">Architecture<a class="headerlink" href="#architecture" title="Permanent link">&para;</a></h2>
<p>The Media API operates as an independent microservice while maintaining data consistency through shared database access:</p>
<pre class="mermaid"><code>flowchart TB
subgraph "Client Layer"
Admin[Admin GUI :3000]
Public[Public Users]
end
subgraph "API Layer"
Express[Express API :4000&lt;br/&gt;Prisma ORM]
Fastify[Fastify Media API :4100&lt;br/&gt;Drizzle ORM]
end
subgraph "Data Layer"
DB[(PostgreSQL 16&lt;br/&gt;v2_changemaker)]
FS[/media/local/library/&lt;br/&gt;Video Files]
end
subgraph "Processing"
FFprobe[FFprobe Service&lt;br/&gt;Metadata Extraction]
end
Admin --&gt;|Media Requests| Fastify
Admin --&gt;|Other Requests| Express
Public --&gt;|View Videos| Fastify
Fastify --&gt;|Drizzle Queries| DB
Express --&gt;|Prisma Queries| DB
Fastify --&gt;|Read/Write| FS
Fastify --&gt;|Extract Metadata| FFprobe
FFprobe --&gt;|Analyze| FS
style Fastify fill:#e74c3c
style Express fill:#3498db
style DB fill:#2ecc71
style FS fill:#f39c12</code></pre>
<p><strong>Architecture Highlights:</strong></p>
<ol>
<li><strong>Port Separation</strong> — Media API on 4100, Main API on 4000</li>
<li><strong>ORM Independence</strong> — Drizzle for media, Prisma for everything else</li>
<li><strong>Shared Database</strong> — Both APIs access same PostgreSQL instance</li>
<li><strong>File System Access</strong> — Media API has direct volume mount to <code>/media/local/library</code></li>
<li><strong>Nginx Routing</strong><code>media.cmlite.org</code> routes to port 4100</li>
</ol>
<p><strong>Why Dual API?</strong></p>
<p>The media system was added after V2 launch as a self-contained enhancement. Keeping it as a separate Fastify microservice:</p>
<ul>
<li>Avoids disrupting the stable Express API</li>
<li>Allows independent scaling and deployment</li>
<li>Provides testing ground for Drizzle ORM migration</li>
<li>Isolates video processing workloads from core application logic</li>
</ul>
<hr />
<h2 id="database-model-drizzle">Database Model (Drizzle)<a class="headerlink" href="#database-model-drizzle" title="Permanent link">&para;</a></h2>
<h3 id="videos-table-schema">Videos Table Schema<a class="headerlink" href="#videos-table-schema" title="Permanent link">&para;</a></h3>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="c1">// api/src/modules/media/db/schema.ts</span>
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">pgTable</span><span class="p">,</span><span class="w"> </span><span class="nx">uuid</span><span class="p">,</span><span class="w"> </span><span class="nx">text</span><span class="p">,</span><span class="w"> </span><span class="nx">integer</span><span class="p">,</span><span class="w"> </span><span class="nx">timestamp</span><span class="p">,</span><span class="w"> </span><span class="kt">boolean</span><span class="p">,</span><span class="w"> </span><span class="nx">jsonb</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;drizzle-orm/pg-core&#39;</span><span class="p">;</span>
</span><span id="__span-0-3"><a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a>
</span><span id="__span-0-4"><a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a><span class="k">export</span><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">videos</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">pgTable</span><span class="p">(</span><span class="s1">&#39;videos&#39;</span><span class="p">,</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-0-5"><a id="__codelineno-0-5" name="__codelineno-0-5" href="#__codelineno-0-5"></a><span class="w"> </span><span class="nx">id</span><span class="o">:</span><span class="w"> </span><span class="kt">uuid</span><span class="p">(</span><span class="s1">&#39;id&#39;</span><span class="p">).</span><span class="nx">primaryKey</span><span class="p">().</span><span class="nx">defaultRandom</span><span class="p">(),</span>
</span><span id="__span-0-6"><a id="__codelineno-0-6" name="__codelineno-0-6" href="#__codelineno-0-6"></a>
</span><span id="__span-0-7"><a id="__codelineno-0-7" name="__codelineno-0-7" href="#__codelineno-0-7"></a><span class="w"> </span><span class="c1">// File Information</span>
</span><span id="__span-0-8"><a id="__codelineno-0-8" name="__codelineno-0-8" href="#__codelineno-0-8"></a><span class="w"> </span><span class="nx">path</span><span class="o">:</span><span class="w"> </span><span class="kt">text</span><span class="p">(</span><span class="s1">&#39;path&#39;</span><span class="p">).</span><span class="nx">notNull</span><span class="p">().</span><span class="nx">unique</span><span class="p">(),</span><span class="w"> </span><span class="c1">// Relative path from library root</span>
</span><span id="__span-0-9"><a id="__codelineno-0-9" name="__codelineno-0-9" href="#__codelineno-0-9"></a><span class="w"> </span><span class="nx">filename</span><span class="o">:</span><span class="w"> </span><span class="kt">text</span><span class="p">(</span><span class="s1">&#39;filename&#39;</span><span class="p">).</span><span class="nx">notNull</span><span class="p">(),</span>
</span><span id="__span-0-10"><a id="__codelineno-0-10" name="__codelineno-0-10" href="#__codelineno-0-10"></a><span class="w"> </span><span class="nx">originalFilename</span><span class="o">:</span><span class="w"> </span><span class="kt">text</span><span class="p">(</span><span class="s1">&#39;original_filename&#39;</span><span class="p">),</span><span class="w"> </span><span class="c1">// User-uploaded filename</span>
</span><span id="__span-0-11"><a id="__codelineno-0-11" name="__codelineno-0-11" href="#__codelineno-0-11"></a><span class="w"> </span><span class="nx">directoryType</span><span class="o">:</span><span class="w"> </span><span class="kt">text</span><span class="p">(</span><span class="s1">&#39;directory_type&#39;</span><span class="p">).</span><span class="nx">notNull</span><span class="p">(),</span><span class="w"> </span><span class="c1">// studios|gifs|private|inbox|curated|playback|compilations|videos|highlights</span>
</span><span id="__span-0-12"><a id="__codelineno-0-12" name="__codelineno-0-12" href="#__codelineno-0-12"></a>
</span><span id="__span-0-13"><a id="__codelineno-0-13" name="__codelineno-0-13" href="#__codelineno-0-13"></a><span class="w"> </span><span class="c1">// Metadata</span>
</span><span id="__span-0-14"><a id="__codelineno-0-14" name="__codelineno-0-14" href="#__codelineno-0-14"></a><span class="w"> </span><span class="nx">producer</span><span class="o">:</span><span class="w"> </span><span class="kt">text</span><span class="p">(</span><span class="s1">&#39;producer&#39;</span><span class="p">),</span>
</span><span id="__span-0-15"><a id="__codelineno-0-15" name="__codelineno-0-15" href="#__codelineno-0-15"></a><span class="w"> </span><span class="nx">creator</span><span class="o">:</span><span class="w"> </span><span class="kt">text</span><span class="p">(</span><span class="s1">&#39;creator&#39;</span><span class="p">),</span>
</span><span id="__span-0-16"><a id="__codelineno-0-16" name="__codelineno-0-16" href="#__codelineno-0-16"></a><span class="w"> </span><span class="nx">title</span><span class="o">:</span><span class="w"> </span><span class="kt">text</span><span class="p">(</span><span class="s1">&#39;title&#39;</span><span class="p">),</span>
</span><span id="__span-0-17"><a id="__codelineno-0-17" name="__codelineno-0-17" href="#__codelineno-0-17"></a><span class="w"> </span><span class="nx">tags</span><span class="o">:</span><span class="w"> </span><span class="kt">jsonb</span><span class="p">(</span><span class="s1">&#39;tags&#39;</span><span class="p">).</span><span class="nx">$type</span><span class="o">&lt;</span><span class="kt">string</span><span class="p">[]</span><span class="o">&gt;</span><span class="p">().</span><span class="k">default</span><span class="p">([]),</span>
</span><span id="__span-0-18"><a id="__codelineno-0-18" name="__codelineno-0-18" href="#__codelineno-0-18"></a>
</span><span id="__span-0-19"><a id="__codelineno-0-19" name="__codelineno-0-19" href="#__codelineno-0-19"></a><span class="w"> </span><span class="c1">// Video Properties</span>
</span><span id="__span-0-20"><a id="__codelineno-0-20" name="__codelineno-0-20" href="#__codelineno-0-20"></a><span class="w"> </span><span class="nx">durationSeconds</span><span class="o">:</span><span class="w"> </span><span class="kt">integer</span><span class="p">(</span><span class="s1">&#39;duration_seconds&#39;</span><span class="p">),</span>
</span><span id="__span-0-21"><a id="__codelineno-0-21" name="__codelineno-0-21" href="#__codelineno-0-21"></a><span class="w"> </span><span class="nx">quality</span><span class="o">:</span><span class="w"> </span><span class="kt">text</span><span class="p">(</span><span class="s1">&#39;quality&#39;</span><span class="p">),</span><span class="w"> </span><span class="c1">// SD|HD|FHD|UHD</span>
</span><span id="__span-0-22"><a id="__codelineno-0-22" name="__codelineno-0-22" href="#__codelineno-0-22"></a><span class="w"> </span><span class="nx">orientation</span><span class="o">:</span><span class="w"> </span><span class="kt">text</span><span class="p">(</span><span class="s1">&#39;orientation&#39;</span><span class="p">),</span><span class="w"> </span><span class="c1">// portrait|landscape|square</span>
</span><span id="__span-0-23"><a id="__codelineno-0-23" name="__codelineno-0-23" href="#__codelineno-0-23"></a><span class="w"> </span><span class="nx">hasAudio</span><span class="o">:</span><span class="w"> </span><span class="kt">boolean</span><span class="p">(</span><span class="s1">&#39;has_audio&#39;</span><span class="p">).</span><span class="k">default</span><span class="p">(</span><span class="kc">false</span><span class="p">),</span>
</span><span id="__span-0-24"><a id="__codelineno-0-24" name="__codelineno-0-24" href="#__codelineno-0-24"></a><span class="w"> </span><span class="nx">width</span><span class="o">:</span><span class="w"> </span><span class="kt">integer</span><span class="p">(</span><span class="s1">&#39;width&#39;</span><span class="p">),</span>
</span><span id="__span-0-25"><a id="__codelineno-0-25" name="__codelineno-0-25" href="#__codelineno-0-25"></a><span class="w"> </span><span class="nx">height</span><span class="o">:</span><span class="w"> </span><span class="kt">integer</span><span class="p">(</span><span class="s1">&#39;height&#39;</span><span class="p">),</span>
</span><span id="__span-0-26"><a id="__codelineno-0-26" name="__codelineno-0-26" href="#__codelineno-0-26"></a>
</span><span id="__span-0-27"><a id="__codelineno-0-27" name="__codelineno-0-27" href="#__codelineno-0-27"></a><span class="w"> </span><span class="c1">// File Details</span>
</span><span id="__span-0-28"><a id="__codelineno-0-28" name="__codelineno-0-28" href="#__codelineno-0-28"></a><span class="w"> </span><span class="nx">fileSize</span><span class="o">:</span><span class="w"> </span><span class="kt">integer</span><span class="p">(</span><span class="s1">&#39;file_size&#39;</span><span class="p">),</span><span class="w"> </span><span class="c1">// Bytes</span>
</span><span id="__span-0-29"><a id="__codelineno-0-29" name="__codelineno-0-29" href="#__codelineno-0-29"></a><span class="w"> </span><span class="nx">fileHash</span><span class="o">:</span><span class="w"> </span><span class="kt">text</span><span class="p">(</span><span class="s1">&#39;file_hash&#39;</span><span class="p">),</span><span class="w"> </span><span class="c1">// SHA-256 for duplicate detection</span>
</span><span id="__span-0-30"><a id="__codelineno-0-30" name="__codelineno-0-30" href="#__codelineno-0-30"></a>
</span><span id="__span-0-31"><a id="__codelineno-0-31" name="__codelineno-0-31" href="#__codelineno-0-31"></a><span class="w"> </span><span class="c1">// Validation</span>
</span><span id="__span-0-32"><a id="__codelineno-0-32" name="__codelineno-0-32" href="#__codelineno-0-32"></a><span class="w"> </span><span class="nx">isValid</span><span class="o">:</span><span class="w"> </span><span class="kt">boolean</span><span class="p">(</span><span class="s1">&#39;is_valid&#39;</span><span class="p">).</span><span class="k">default</span><span class="p">(</span><span class="kc">true</span><span class="p">),</span>
</span><span id="__span-0-33"><a id="__codelineno-0-33" name="__codelineno-0-33" href="#__codelineno-0-33"></a><span class="w"> </span><span class="nx">lastValidated</span><span class="o">:</span><span class="w"> </span><span class="kt">timestamp</span><span class="p">(</span><span class="s1">&#39;last_validated&#39;</span><span class="p">),</span>
</span><span id="__span-0-34"><a id="__codelineno-0-34" name="__codelineno-0-34" href="#__codelineno-0-34"></a><span class="w"> </span><span class="nx">standardizedAt</span><span class="o">:</span><span class="w"> </span><span class="kt">timestamp</span><span class="p">(</span><span class="s1">&#39;standardized_at&#39;</span><span class="p">),</span><span class="w"> </span><span class="c1">// When file was moved to standard location</span>
</span><span id="__span-0-35"><a id="__codelineno-0-35" name="__codelineno-0-35" href="#__codelineno-0-35"></a>
</span><span id="__span-0-36"><a id="__codelineno-0-36" name="__codelineno-0-36" href="#__codelineno-0-36"></a><span class="w"> </span><span class="c1">// Thumbnail</span>
</span><span id="__span-0-37"><a id="__codelineno-0-37" name="__codelineno-0-37" href="#__codelineno-0-37"></a><span class="w"> </span><span class="nx">thumbnailPath</span><span class="o">:</span><span class="w"> </span><span class="kt">text</span><span class="p">(</span><span class="s1">&#39;thumbnail_path&#39;</span><span class="p">),</span>
</span><span id="__span-0-38"><a id="__codelineno-0-38" name="__codelineno-0-38" href="#__codelineno-0-38"></a>
</span><span id="__span-0-39"><a id="__codelineno-0-39" name="__codelineno-0-39" href="#__codelineno-0-39"></a><span class="w"> </span><span class="c1">// Public Sharing</span>
</span><span id="__span-0-40"><a id="__codelineno-0-40" name="__codelineno-0-40" href="#__codelineno-0-40"></a><span class="w"> </span><span class="nx">publicViewCount</span><span class="o">:</span><span class="w"> </span><span class="kt">integer</span><span class="p">(</span><span class="s1">&#39;public_view_count&#39;</span><span class="p">).</span><span class="k">default</span><span class="p">(</span><span class="mf">0</span><span class="p">),</span>
</span><span id="__span-0-41"><a id="__codelineno-0-41" name="__codelineno-0-41" href="#__codelineno-0-41"></a><span class="w"> </span><span class="nx">publicUpvoteCount</span><span class="o">:</span><span class="w"> </span><span class="kt">integer</span><span class="p">(</span><span class="s1">&#39;public_upvote_count&#39;</span><span class="p">).</span><span class="k">default</span><span class="p">(</span><span class="mf">0</span><span class="p">),</span>
</span><span id="__span-0-42"><a id="__codelineno-0-42" name="__codelineno-0-42" href="#__codelineno-0-42"></a><span class="w"> </span><span class="nx">movedFromPublicAt</span><span class="o">:</span><span class="w"> </span><span class="kt">timestamp</span><span class="p">(</span><span class="s1">&#39;moved_from_public_at&#39;</span><span class="p">),</span><span class="w"> </span><span class="c1">// When video was unlocked from public</span>
</span><span id="__span-0-43"><a id="__codelineno-0-43" name="__codelineno-0-43" href="#__codelineno-0-43"></a>
</span><span id="__span-0-44"><a id="__codelineno-0-44" name="__codelineno-0-44" href="#__codelineno-0-44"></a><span class="w"> </span><span class="c1">// Timestamps</span>
</span><span id="__span-0-45"><a id="__codelineno-0-45" name="__codelineno-0-45" href="#__codelineno-0-45"></a><span class="w"> </span><span class="nx">createdAt</span><span class="o">:</span><span class="w"> </span><span class="kt">timestamp</span><span class="p">(</span><span class="s1">&#39;created_at&#39;</span><span class="p">).</span><span class="nx">defaultNow</span><span class="p">(),</span>
</span><span id="__span-0-46"><a id="__codelineno-0-46" name="__codelineno-0-46" href="#__codelineno-0-46"></a><span class="w"> </span><span class="nx">updatedAt</span><span class="o">:</span><span class="w"> </span><span class="kt">timestamp</span><span class="p">(</span><span class="s1">&#39;updated_at&#39;</span><span class="p">).</span><span class="nx">defaultNow</span><span class="p">(),</span>
</span><span id="__span-0-47"><a id="__codelineno-0-47" name="__codelineno-0-47" href="#__codelineno-0-47"></a><span class="p">});</span>
</span></code></pre></div>
<h3 id="directory-types-enum">Directory Types Enum<a class="headerlink" href="#directory-types-enum" title="Permanent link">&para;</a></h3>
<table>
<thead>
<tr>
<th>Directory Type</th>
<th>Purpose</th>
<th>Public Eligible</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>studios</code></td>
<td>Studio-organized content</td>
<td></td>
</tr>
<tr>
<td><code>gifs</code></td>
<td>Short looping videos</td>
<td></td>
</tr>
<tr>
<td><code>private</code></td>
<td>Private/unreleased content</td>
<td></td>
</tr>
<tr>
<td><code>inbox</code></td>
<td>Upload staging area</td>
<td></td>
</tr>
<tr>
<td><code>curated</code></td>
<td>Hand-picked highlights</td>
<td></td>
</tr>
<tr>
<td><code>playback</code></td>
<td>Playback-optimized encodes</td>
<td></td>
</tr>
<tr>
<td><code>compilations</code></td>
<td>Multi-video compilations</td>
<td></td>
</tr>
<tr>
<td><code>videos</code></td>
<td>General video library</td>
<td></td>
</tr>
<tr>
<td><code>highlights</code></td>
<td>Auto-generated highlights</td>
<td></td>
</tr>
</tbody>
</table>
<h3 id="quality-classifications">Quality Classifications<a class="headerlink" href="#quality-classifications" title="Permanent link">&para;</a></h3>
<table>
<thead>
<tr>
<th>Quality</th>
<th>Height Range</th>
<th>Typical Resolution</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>SD</code></td>
<td>&lt; 720px</td>
<td>480p, 576p</td>
</tr>
<tr>
<td><code>HD</code></td>
<td>720px - 1079px</td>
<td>720p</td>
</tr>
<tr>
<td><code>FHD</code></td>
<td>1080px - 2159px</td>
<td>1080p</td>
</tr>
<tr>
<td><code>UHD</code></td>
<td>≥ 2160px</td>
<td>4K, 8K</td>
</tr>
</tbody>
</table>
<h3 id="orientation-detection">Orientation Detection<a class="headerlink" href="#orientation-detection" title="Permanent link">&para;</a></h3>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1" href="#__codelineno-1-1"></a><span class="kd">const</span><span class="w"> </span><span class="nx">detectOrientation</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="nx">width</span><span class="o">:</span><span class="w"> </span><span class="kt">number</span><span class="p">,</span><span class="w"> </span><span class="nx">height</span><span class="o">:</span><span class="w"> </span><span class="kt">number</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-1-2"><a id="__codelineno-1-2" name="__codelineno-1-2" href="#__codelineno-1-2"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">ratio</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">width</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="nx">height</span><span class="p">;</span>
</span><span id="__span-1-3"><a id="__codelineno-1-3" name="__codelineno-1-3" href="#__codelineno-1-3"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">ratio</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mf">1.1</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s1">&#39;landscape&#39;</span><span class="p">;</span>
</span><span id="__span-1-4"><a id="__codelineno-1-4" name="__codelineno-1-4" href="#__codelineno-1-4"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">ratio</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="mf">0.9</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s1">&#39;portrait&#39;</span><span class="p">;</span>
</span><span id="__span-1-5"><a id="__codelineno-1-5" name="__codelineno-1-5" href="#__codelineno-1-5"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s1">&#39;square&#39;</span><span class="p">;</span>
</span><span id="__span-1-6"><a id="__codelineno-1-6" name="__codelineno-1-6" href="#__codelineno-1-6"></a><span class="p">};</span>
</span></code></pre></div>
<hr />
<h2 id="api-endpoints">API Endpoints<a class="headerlink" href="#api-endpoints" title="Permanent link">&para;</a></h2>
<p>All endpoints require authentication with <code>SUPER_ADMIN</code> role unless marked as public.</p>
<h3 id="list-videos">List Videos<a class="headerlink" href="#list-videos" title="Permanent link">&para;</a></h3>
<div class="language-http highlight"><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1" href="#__codelineno-2-1"></a><span class="err">GET /api/media/videos</span>
</span></code></pre></div>
<p><strong>Query Parameters:</strong></p>
<table>
<thead>
<tr>
<th>Parameter</th>
<th>Type</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>page</code></td>
<td>number</td>
<td>1</td>
<td>Page number for pagination</td>
</tr>
<tr>
<td><code>limit</code></td>
<td>number</td>
<td>20</td>
<td>Results per page (max 100)</td>
</tr>
<tr>
<td><code>directoryType</code></td>
<td>string</td>
<td>-</td>
<td>Filter by directory (studios, gifs, etc.)</td>
</tr>
<tr>
<td><code>orientation</code></td>
<td>string</td>
<td>-</td>
<td>Filter by orientation (portrait, landscape, square)</td>
</tr>
<tr>
<td><code>producer</code></td>
<td>string</td>
<td>-</td>
<td>Filter by producer (partial match)</td>
</tr>
<tr>
<td><code>creator</code></td>
<td>string</td>
<td>-</td>
<td>Filter by creator (partial match)</td>
</tr>
<tr>
<td><code>quality</code></td>
<td>string</td>
<td>-</td>
<td>Filter by quality (SD, HD, FHD, UHD)</td>
</tr>
<tr>
<td><code>hasAudio</code></td>
<td>boolean</td>
<td>-</td>
<td>Filter by audio presence</td>
</tr>
<tr>
<td><code>isValid</code></td>
<td>boolean</td>
<td>true</td>
<td>Filter by validation status</td>
</tr>
<tr>
<td><code>search</code></td>
<td>string</td>
<td>-</td>
<td>Search in title, producer, creator</td>
</tr>
</tbody>
</table>
<p><strong>Response:</strong></p>
<div class="language-json highlight"><pre><span></span><code><span id="__span-3-1"><a id="__codelineno-3-1" name="__codelineno-3-1" href="#__codelineno-3-1"></a><span class="p">{</span>
</span><span id="__span-3-2"><a id="__codelineno-3-2" name="__codelineno-3-2" href="#__codelineno-3-2"></a><span class="w"> </span><span class="nt">&quot;data&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
</span><span id="__span-3-3"><a id="__codelineno-3-3" name="__codelineno-3-3" href="#__codelineno-3-3"></a><span class="w"> </span><span class="p">{</span>
</span><span id="__span-3-4"><a id="__codelineno-3-4" name="__codelineno-3-4" href="#__codelineno-3-4"></a><span class="w"> </span><span class="nt">&quot;id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;550e8400-e29b-41d4-a716-446655440000&quot;</span><span class="p">,</span>
</span><span id="__span-3-5"><a id="__codelineno-3-5" name="__codelineno-3-5" href="#__codelineno-3-5"></a><span class="w"> </span><span class="nt">&quot;path&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;videos/sample.mp4&quot;</span><span class="p">,</span>
</span><span id="__span-3-6"><a id="__codelineno-3-6" name="__codelineno-3-6" href="#__codelineno-3-6"></a><span class="w"> </span><span class="nt">&quot;filename&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sample.mp4&quot;</span><span class="p">,</span>
</span><span id="__span-3-7"><a id="__codelineno-3-7" name="__codelineno-3-7" href="#__codelineno-3-7"></a><span class="w"> </span><span class="nt">&quot;directoryType&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;videos&quot;</span><span class="p">,</span>
</span><span id="__span-3-8"><a id="__codelineno-3-8" name="__codelineno-3-8" href="#__codelineno-3-8"></a><span class="w"> </span><span class="nt">&quot;producer&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Studio A&quot;</span><span class="p">,</span>
</span><span id="__span-3-9"><a id="__codelineno-3-9" name="__codelineno-3-9" href="#__codelineno-3-9"></a><span class="w"> </span><span class="nt">&quot;creator&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Director B&quot;</span><span class="p">,</span>
</span><span id="__span-3-10"><a id="__codelineno-3-10" name="__codelineno-3-10" href="#__codelineno-3-10"></a><span class="w"> </span><span class="nt">&quot;title&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Sample Video&quot;</span><span class="p">,</span>
</span><span id="__span-3-11"><a id="__codelineno-3-11" name="__codelineno-3-11" href="#__codelineno-3-11"></a><span class="w"> </span><span class="nt">&quot;durationSeconds&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">180</span><span class="p">,</span>
</span><span id="__span-3-12"><a id="__codelineno-3-12" name="__codelineno-3-12" href="#__codelineno-3-12"></a><span class="w"> </span><span class="nt">&quot;quality&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;FHD&quot;</span><span class="p">,</span>
</span><span id="__span-3-13"><a id="__codelineno-3-13" name="__codelineno-3-13" href="#__codelineno-3-13"></a><span class="w"> </span><span class="nt">&quot;orientation&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;landscape&quot;</span><span class="p">,</span>
</span><span id="__span-3-14"><a id="__codelineno-3-14" name="__codelineno-3-14" href="#__codelineno-3-14"></a><span class="w"> </span><span class="nt">&quot;hasAudio&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
</span><span id="__span-3-15"><a id="__codelineno-3-15" name="__codelineno-3-15" href="#__codelineno-3-15"></a><span class="w"> </span><span class="nt">&quot;width&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1920</span><span class="p">,</span>
</span><span id="__span-3-16"><a id="__codelineno-3-16" name="__codelineno-3-16" href="#__codelineno-3-16"></a><span class="w"> </span><span class="nt">&quot;height&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1080</span><span class="p">,</span>
</span><span id="__span-3-17"><a id="__codelineno-3-17" name="__codelineno-3-17" href="#__codelineno-3-17"></a><span class="w"> </span><span class="nt">&quot;fileSize&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">52428800</span><span class="p">,</span>
</span><span id="__span-3-18"><a id="__codelineno-3-18" name="__codelineno-3-18" href="#__codelineno-3-18"></a><span class="w"> </span><span class="nt">&quot;isValid&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
</span><span id="__span-3-19"><a id="__codelineno-3-19" name="__codelineno-3-19" href="#__codelineno-3-19"></a><span class="w"> </span><span class="nt">&quot;createdAt&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2026-02-10T12:00:00Z&quot;</span>
</span><span id="__span-3-20"><a id="__codelineno-3-20" name="__codelineno-3-20" href="#__codelineno-3-20"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-3-21"><a id="__codelineno-3-21" name="__codelineno-3-21" href="#__codelineno-3-21"></a><span class="w"> </span><span class="p">],</span>
</span><span id="__span-3-22"><a id="__codelineno-3-22" name="__codelineno-3-22" href="#__codelineno-3-22"></a><span class="w"> </span><span class="nt">&quot;pagination&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-3-23"><a id="__codelineno-3-23" name="__codelineno-3-23" href="#__codelineno-3-23"></a><span class="w"> </span><span class="nt">&quot;page&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
</span><span id="__span-3-24"><a id="__codelineno-3-24" name="__codelineno-3-24" href="#__codelineno-3-24"></a><span class="w"> </span><span class="nt">&quot;limit&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span>
</span><span id="__span-3-25"><a id="__codelineno-3-25" name="__codelineno-3-25" href="#__codelineno-3-25"></a><span class="w"> </span><span class="nt">&quot;total&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">156</span><span class="p">,</span>
</span><span id="__span-3-26"><a id="__codelineno-3-26" name="__codelineno-3-26" href="#__codelineno-3-26"></a><span class="w"> </span><span class="nt">&quot;totalPages&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span>
</span><span id="__span-3-27"><a id="__codelineno-3-27" name="__codelineno-3-27" href="#__codelineno-3-27"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-3-28"><a id="__codelineno-3-28" name="__codelineno-3-28" href="#__codelineno-3-28"></a><span class="p">}</span>
</span></code></pre></div>
<hr />
<h3 id="get-video-details">Get Video Details<a class="headerlink" href="#get-video-details" title="Permanent link">&para;</a></h3>
<div class="language-http highlight"><pre><span></span><code><span id="__span-4-1"><a id="__codelineno-4-1" name="__codelineno-4-1" href="#__codelineno-4-1"></a><span class="err">GET /api/media/videos/:id</span>
</span></code></pre></div>
<p><strong>Response:</strong></p>
<div class="language-json highlight"><pre><span></span><code><span id="__span-5-1"><a id="__codelineno-5-1" name="__codelineno-5-1" href="#__codelineno-5-1"></a><span class="p">{</span>
</span><span id="__span-5-2"><a id="__codelineno-5-2" name="__codelineno-5-2" href="#__codelineno-5-2"></a><span class="w"> </span><span class="nt">&quot;id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;550e8400-e29b-41d4-a716-446655440000&quot;</span><span class="p">,</span>
</span><span id="__span-5-3"><a id="__codelineno-5-3" name="__codelineno-5-3" href="#__codelineno-5-3"></a><span class="w"> </span><span class="nt">&quot;path&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;videos/sample.mp4&quot;</span><span class="p">,</span>
</span><span id="__span-5-4"><a id="__codelineno-5-4" name="__codelineno-5-4" href="#__codelineno-5-4"></a><span class="w"> </span><span class="nt">&quot;filename&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sample.mp4&quot;</span><span class="p">,</span>
</span><span id="__span-5-5"><a id="__codelineno-5-5" name="__codelineno-5-5" href="#__codelineno-5-5"></a><span class="w"> </span><span class="nt">&quot;originalFilename&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;my-video.mp4&quot;</span><span class="p">,</span>
</span><span id="__span-5-6"><a id="__codelineno-5-6" name="__codelineno-5-6" href="#__codelineno-5-6"></a><span class="w"> </span><span class="nt">&quot;directoryType&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;videos&quot;</span><span class="p">,</span>
</span><span id="__span-5-7"><a id="__codelineno-5-7" name="__codelineno-5-7" href="#__codelineno-5-7"></a><span class="w"> </span><span class="nt">&quot;producer&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Studio A&quot;</span><span class="p">,</span>
</span><span id="__span-5-8"><a id="__codelineno-5-8" name="__codelineno-5-8" href="#__codelineno-5-8"></a><span class="w"> </span><span class="nt">&quot;creator&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Director B&quot;</span><span class="p">,</span>
</span><span id="__span-5-9"><a id="__codelineno-5-9" name="__codelineno-5-9" href="#__codelineno-5-9"></a><span class="w"> </span><span class="nt">&quot;title&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Sample Video&quot;</span><span class="p">,</span>
</span><span id="__span-5-10"><a id="__codelineno-5-10" name="__codelineno-5-10" href="#__codelineno-5-10"></a><span class="w"> </span><span class="nt">&quot;tags&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&quot;action&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;sports&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;highlight&quot;</span><span class="p">],</span>
</span><span id="__span-5-11"><a id="__codelineno-5-11" name="__codelineno-5-11" href="#__codelineno-5-11"></a><span class="w"> </span><span class="nt">&quot;durationSeconds&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">180</span><span class="p">,</span>
</span><span id="__span-5-12"><a id="__codelineno-5-12" name="__codelineno-5-12" href="#__codelineno-5-12"></a><span class="w"> </span><span class="nt">&quot;quality&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;FHD&quot;</span><span class="p">,</span>
</span><span id="__span-5-13"><a id="__codelineno-5-13" name="__codelineno-5-13" href="#__codelineno-5-13"></a><span class="w"> </span><span class="nt">&quot;orientation&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;landscape&quot;</span><span class="p">,</span>
</span><span id="__span-5-14"><a id="__codelineno-5-14" name="__codelineno-5-14" href="#__codelineno-5-14"></a><span class="w"> </span><span class="nt">&quot;hasAudio&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
</span><span id="__span-5-15"><a id="__codelineno-5-15" name="__codelineno-5-15" href="#__codelineno-5-15"></a><span class="w"> </span><span class="nt">&quot;width&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1920</span><span class="p">,</span>
</span><span id="__span-5-16"><a id="__codelineno-5-16" name="__codelineno-5-16" href="#__codelineno-5-16"></a><span class="w"> </span><span class="nt">&quot;height&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1080</span><span class="p">,</span>
</span><span id="__span-5-17"><a id="__codelineno-5-17" name="__codelineno-5-17" href="#__codelineno-5-17"></a><span class="w"> </span><span class="nt">&quot;fileSize&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">52428800</span><span class="p">,</span>
</span><span id="__span-5-18"><a id="__codelineno-5-18" name="__codelineno-5-18" href="#__codelineno-5-18"></a><span class="w"> </span><span class="nt">&quot;fileHash&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;a3d2f1e8b9c7...&quot;</span><span class="p">,</span>
</span><span id="__span-5-19"><a id="__codelineno-5-19" name="__codelineno-5-19" href="#__codelineno-5-19"></a><span class="w"> </span><span class="nt">&quot;isValid&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
</span><span id="__span-5-20"><a id="__codelineno-5-20" name="__codelineno-5-20" href="#__codelineno-5-20"></a><span class="w"> </span><span class="nt">&quot;lastValidated&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2026-02-10T12:00:00Z&quot;</span><span class="p">,</span>
</span><span id="__span-5-21"><a id="__codelineno-5-21" name="__codelineno-5-21" href="#__codelineno-5-21"></a><span class="w"> </span><span class="nt">&quot;thumbnailPath&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;thumbnails/550e8400.jpg&quot;</span><span class="p">,</span>
</span><span id="__span-5-22"><a id="__codelineno-5-22" name="__codelineno-5-22" href="#__codelineno-5-22"></a><span class="w"> </span><span class="nt">&quot;publicViewCount&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1250</span><span class="p">,</span>
</span><span id="__span-5-23"><a id="__codelineno-5-23" name="__codelineno-5-23" href="#__codelineno-5-23"></a><span class="w"> </span><span class="nt">&quot;publicUpvoteCount&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">85</span><span class="p">,</span>
</span><span id="__span-5-24"><a id="__codelineno-5-24" name="__codelineno-5-24" href="#__codelineno-5-24"></a><span class="w"> </span><span class="nt">&quot;createdAt&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2026-02-10T12:00:00Z&quot;</span><span class="p">,</span>
</span><span id="__span-5-25"><a id="__codelineno-5-25" name="__codelineno-5-25" href="#__codelineno-5-25"></a><span class="w"> </span><span class="nt">&quot;updatedAt&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2026-02-10T12:00:00Z&quot;</span>
</span><span id="__span-5-26"><a id="__codelineno-5-26" name="__codelineno-5-26" href="#__codelineno-5-26"></a><span class="p">}</span>
</span></code></pre></div>
<hr />
<h3 id="create-video-record">Create Video Record<a class="headerlink" href="#create-video-record" title="Permanent link">&para;</a></h3>
<div class="language-http highlight"><pre><span></span><code><span id="__span-6-1"><a id="__codelineno-6-1" name="__codelineno-6-1" href="#__codelineno-6-1"></a><span class="err">POST /api/media/videos</span>
</span></code></pre></div>
<p><strong>Request Body:</strong></p>
<div class="language-json highlight"><pre><span></span><code><span id="__span-7-1"><a id="__codelineno-7-1" name="__codelineno-7-1" href="#__codelineno-7-1"></a><span class="p">{</span>
</span><span id="__span-7-2"><a id="__codelineno-7-2" name="__codelineno-7-2" href="#__codelineno-7-2"></a><span class="w"> </span><span class="nt">&quot;path&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;videos/new-video.mp4&quot;</span><span class="p">,</span>
</span><span id="__span-7-3"><a id="__codelineno-7-3" name="__codelineno-7-3" href="#__codelineno-7-3"></a><span class="w"> </span><span class="nt">&quot;filename&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;new-video.mp4&quot;</span><span class="p">,</span>
</span><span id="__span-7-4"><a id="__codelineno-7-4" name="__codelineno-7-4" href="#__codelineno-7-4"></a><span class="w"> </span><span class="nt">&quot;directoryType&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;videos&quot;</span><span class="p">,</span>
</span><span id="__span-7-5"><a id="__codelineno-7-5" name="__codelineno-7-5" href="#__codelineno-7-5"></a><span class="w"> </span><span class="nt">&quot;producer&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Studio A&quot;</span><span class="p">,</span>
</span><span id="__span-7-6"><a id="__codelineno-7-6" name="__codelineno-7-6" href="#__codelineno-7-6"></a><span class="w"> </span><span class="nt">&quot;creator&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Director B&quot;</span><span class="p">,</span>
</span><span id="__span-7-7"><a id="__codelineno-7-7" name="__codelineno-7-7" href="#__codelineno-7-7"></a><span class="w"> </span><span class="nt">&quot;title&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;New Video&quot;</span><span class="p">,</span>
</span><span id="__span-7-8"><a id="__codelineno-7-8" name="__codelineno-7-8" href="#__codelineno-7-8"></a><span class="w"> </span><span class="nt">&quot;tags&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&quot;action&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;sports&quot;</span><span class="p">]</span>
</span><span id="__span-7-9"><a id="__codelineno-7-9" name="__codelineno-7-9" href="#__codelineno-7-9"></a><span class="p">}</span>
</span></code></pre></div>
<p><strong>Notes:</strong></p>
<ul>
<li>File must already exist at specified path on filesystem</li>
<li>FFprobe metadata extraction runs automatically after creation</li>
<li>Use <code>/api/media/upload/single</code> for file upload + record creation</li>
</ul>
<p><strong>Response:</strong></p>
<div class="language-json highlight"><pre><span></span><code><span id="__span-8-1"><a id="__codelineno-8-1" name="__codelineno-8-1" href="#__codelineno-8-1"></a><span class="p">{</span>
</span><span id="__span-8-2"><a id="__codelineno-8-2" name="__codelineno-8-2" href="#__codelineno-8-2"></a><span class="w"> </span><span class="nt">&quot;id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;660e8400-e29b-41d4-a716-446655440000&quot;</span><span class="p">,</span>
</span><span id="__span-8-3"><a id="__codelineno-8-3" name="__codelineno-8-3" href="#__codelineno-8-3"></a><span class="w"> </span><span class="nt">&quot;path&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;videos/new-video.mp4&quot;</span><span class="p">,</span>
</span><span id="__span-8-4"><a id="__codelineno-8-4" name="__codelineno-8-4" href="#__codelineno-8-4"></a><span class="w"> </span><span class="nt">&quot;filename&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;new-video.mp4&quot;</span><span class="p">,</span>
</span><span id="__span-8-5"><a id="__codelineno-8-5" name="__codelineno-8-5" href="#__codelineno-8-5"></a><span class="w"> </span><span class="nt">&quot;directoryType&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;videos&quot;</span><span class="p">,</span>
</span><span id="__span-8-6"><a id="__codelineno-8-6" name="__codelineno-8-6" href="#__codelineno-8-6"></a><span class="w"> </span><span class="nt">&quot;isValid&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
</span><span id="__span-8-7"><a id="__codelineno-8-7" name="__codelineno-8-7" href="#__codelineno-8-7"></a><span class="w"> </span><span class="nt">&quot;createdAt&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2026-02-13T10:30:00Z&quot;</span>
</span><span id="__span-8-8"><a id="__codelineno-8-8" name="__codelineno-8-8" href="#__codelineno-8-8"></a><span class="p">}</span>
</span></code></pre></div>
<hr />
<h3 id="update-video-metadata">Update Video Metadata<a class="headerlink" href="#update-video-metadata" title="Permanent link">&para;</a></h3>
<div class="language-http highlight"><pre><span></span><code><span id="__span-9-1"><a id="__codelineno-9-1" name="__codelineno-9-1" href="#__codelineno-9-1"></a><span class="err">PUT /api/media/videos/:id</span>
</span></code></pre></div>
<p><strong>Request Body:</strong></p>
<div class="language-json highlight"><pre><span></span><code><span id="__span-10-1"><a id="__codelineno-10-1" name="__codelineno-10-1" href="#__codelineno-10-1"></a><span class="p">{</span>
</span><span id="__span-10-2"><a id="__codelineno-10-2" name="__codelineno-10-2" href="#__codelineno-10-2"></a><span class="w"> </span><span class="nt">&quot;producer&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Updated Studio&quot;</span><span class="p">,</span>
</span><span id="__span-10-3"><a id="__codelineno-10-3" name="__codelineno-10-3" href="#__codelineno-10-3"></a><span class="w"> </span><span class="nt">&quot;creator&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;New Director&quot;</span><span class="p">,</span>
</span><span id="__span-10-4"><a id="__codelineno-10-4" name="__codelineno-10-4" href="#__codelineno-10-4"></a><span class="w"> </span><span class="nt">&quot;title&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Updated Title&quot;</span><span class="p">,</span>
</span><span id="__span-10-5"><a id="__codelineno-10-5" name="__codelineno-10-5" href="#__codelineno-10-5"></a><span class="w"> </span><span class="nt">&quot;tags&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&quot;updated&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;tags&quot;</span><span class="p">]</span>
</span><span id="__span-10-6"><a id="__codelineno-10-6" name="__codelineno-10-6" href="#__codelineno-10-6"></a><span class="p">}</span>
</span></code></pre></div>
<p><strong>Updatable Fields:</strong></p>
<ul>
<li><code>producer</code> — Video producer/studio</li>
<li><code>creator</code> — Director/creator name</li>
<li><code>title</code> — Display title</li>
<li><code>tags</code> — Array of tag strings</li>
<li><code>thumbnailPath</code> — Custom thumbnail path</li>
</ul>
<p><strong>Response:</strong></p>
<div class="language-json highlight"><pre><span></span><code><span id="__span-11-1"><a id="__codelineno-11-1" name="__codelineno-11-1" href="#__codelineno-11-1"></a><span class="p">{</span>
</span><span id="__span-11-2"><a id="__codelineno-11-2" name="__codelineno-11-2" href="#__codelineno-11-2"></a><span class="w"> </span><span class="nt">&quot;id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;550e8400-e29b-41d4-a716-446655440000&quot;</span><span class="p">,</span>
</span><span id="__span-11-3"><a id="__codelineno-11-3" name="__codelineno-11-3" href="#__codelineno-11-3"></a><span class="w"> </span><span class="nt">&quot;producer&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Updated Studio&quot;</span><span class="p">,</span>
</span><span id="__span-11-4"><a id="__codelineno-11-4" name="__codelineno-11-4" href="#__codelineno-11-4"></a><span class="w"> </span><span class="nt">&quot;creator&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;New Director&quot;</span><span class="p">,</span>
</span><span id="__span-11-5"><a id="__codelineno-11-5" name="__codelineno-11-5" href="#__codelineno-11-5"></a><span class="w"> </span><span class="nt">&quot;title&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Updated Title&quot;</span><span class="p">,</span>
</span><span id="__span-11-6"><a id="__codelineno-11-6" name="__codelineno-11-6" href="#__codelineno-11-6"></a><span class="w"> </span><span class="nt">&quot;tags&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&quot;updated&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;tags&quot;</span><span class="p">],</span>
</span><span id="__span-11-7"><a id="__codelineno-11-7" name="__codelineno-11-7" href="#__codelineno-11-7"></a><span class="w"> </span><span class="nt">&quot;updatedAt&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2026-02-13T10:35:00Z&quot;</span>
</span><span id="__span-11-8"><a id="__codelineno-11-8" name="__codelineno-11-8" href="#__codelineno-11-8"></a><span class="p">}</span>
</span></code></pre></div>
<hr />
<h3 id="delete-video">Delete Video<a class="headerlink" href="#delete-video" title="Permanent link">&para;</a></h3>
<div class="language-http highlight"><pre><span></span><code><span id="__span-12-1"><a id="__codelineno-12-1" name="__codelineno-12-1" href="#__codelineno-12-1"></a><span class="err">DELETE /api/media/videos/:id</span>
</span></code></pre></div>
<p><strong>Behavior:</strong></p>
<ul>
<li><strong>Soft Delete</strong> — Sets <code>isValid = false</code> instead of removing record</li>
<li>File remains on filesystem (manual cleanup required)</li>
<li>Video no longer appears in default listings</li>
<li>Can be restored by setting <code>isValid = true</code> via database</li>
</ul>
<p><strong>Response:</strong></p>
<div class="language-json highlight"><pre><span></span><code><span id="__span-13-1"><a id="__codelineno-13-1" name="__codelineno-13-1" href="#__codelineno-13-1"></a><span class="p">{</span>
</span><span id="__span-13-2"><a id="__codelineno-13-2" name="__codelineno-13-2" href="#__codelineno-13-2"></a><span class="w"> </span><span class="nt">&quot;success&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
</span><span id="__span-13-3"><a id="__codelineno-13-3" name="__codelineno-13-3" href="#__codelineno-13-3"></a><span class="w"> </span><span class="nt">&quot;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Video marked as invalid&quot;</span>
</span><span id="__span-13-4"><a id="__codelineno-13-4" name="__codelineno-13-4" href="#__codelineno-13-4"></a><span class="p">}</span>
</span></code></pre></div>
<hr />
<h3 id="scan-directory">Scan Directory<a class="headerlink" href="#scan-directory" title="Permanent link">&para;</a></h3>
<div class="language-http highlight"><pre><span></span><code><span id="__span-14-1"><a id="__codelineno-14-1" name="__codelineno-14-1" href="#__codelineno-14-1"></a><span class="err">POST /api/media/videos/scan</span>
</span></code></pre></div>
<p><strong>Request Body:</strong></p>
<div class="language-json highlight"><pre><span></span><code><span id="__span-15-1"><a id="__codelineno-15-1" name="__codelineno-15-1" href="#__codelineno-15-1"></a><span class="p">{</span>
</span><span id="__span-15-2"><a id="__codelineno-15-2" name="__codelineno-15-2" href="#__codelineno-15-2"></a><span class="w"> </span><span class="nt">&quot;directoryType&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;videos&quot;</span><span class="p">,</span>
</span><span id="__span-15-3"><a id="__codelineno-15-3" name="__codelineno-15-3" href="#__codelineno-15-3"></a><span class="w"> </span><span class="nt">&quot;skipExisting&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span>
</span><span id="__span-15-4"><a id="__codelineno-15-4" name="__codelineno-15-4" href="#__codelineno-15-4"></a><span class="p">}</span>
</span></code></pre></div>
<p><strong>Parameters:</strong></p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Required</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>directoryType</code></td>
<td>string</td>
<td></td>
<td>Directory to scan (videos, studios, etc.)</td>
</tr>
<tr>
<td><code>skipExisting</code></td>
<td>boolean</td>
<td>-</td>
<td>Skip files already in database (default: true)</td>
</tr>
</tbody>
</table>
<p><strong>Process:</strong></p>
<ol>
<li>Reads filesystem directory <code>/media/local/library/{directoryType}/</code></li>
<li>Filters for video extensions (<code>.mp4</code>, <code>.mov</code>, <code>.avi</code>, <code>.mkv</code>, <code>.webm</code>, <code>.m4v</code>, <code>.flv</code>)</li>
<li>Checks each file against database (by path)</li>
<li>Creates records for new files</li>
<li>Runs FFprobe metadata extraction on new records</li>
</ol>
<p><strong>Response:</strong></p>
<div class="language-json highlight"><pre><span></span><code><span id="__span-16-1"><a id="__codelineno-16-1" name="__codelineno-16-1" href="#__codelineno-16-1"></a><span class="p">{</span>
</span><span id="__span-16-2"><a id="__codelineno-16-2" name="__codelineno-16-2" href="#__codelineno-16-2"></a><span class="w"> </span><span class="nt">&quot;scanned&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">45</span><span class="p">,</span>
</span><span id="__span-16-3"><a id="__codelineno-16-3" name="__codelineno-16-3" href="#__codelineno-16-3"></a><span class="w"> </span><span class="nt">&quot;created&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span>
</span><span id="__span-16-4"><a id="__codelineno-16-4" name="__codelineno-16-4" href="#__codelineno-16-4"></a><span class="w"> </span><span class="nt">&quot;skipped&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">33</span><span class="p">,</span>
</span><span id="__span-16-5"><a id="__codelineno-16-5" name="__codelineno-16-5" href="#__codelineno-16-5"></a><span class="w"> </span><span class="nt">&quot;failed&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span>
</span><span id="__span-16-6"><a id="__codelineno-16-6" name="__codelineno-16-6" href="#__codelineno-16-6"></a><span class="w"> </span><span class="nt">&quot;errors&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span>
</span><span id="__span-16-7"><a id="__codelineno-16-7" name="__codelineno-16-7" href="#__codelineno-16-7"></a><span class="p">}</span>
</span></code></pre></div>
<hr />
<h3 id="validate-video">Validate Video<a class="headerlink" href="#validate-video" title="Permanent link">&para;</a></h3>
<div class="language-http highlight"><pre><span></span><code><span id="__span-17-1"><a id="__codelineno-17-1" name="__codelineno-17-1" href="#__codelineno-17-1"></a><span class="err">POST /api/media/videos/:id/validate</span>
</span></code></pre></div>
<p><strong>Purpose:</strong></p>
<ul>
<li>Re-run FFprobe metadata extraction</li>
<li>Update video properties (duration, dimensions, etc.)</li>
<li>Verify file still exists and is readable</li>
<li>Refresh file size and hash</li>
</ul>
<p><strong>Response:</strong></p>
<div class="language-json highlight"><pre><span></span><code><span id="__span-18-1"><a id="__codelineno-18-1" name="__codelineno-18-1" href="#__codelineno-18-1"></a><span class="p">{</span>
</span><span id="__span-18-2"><a id="__codelineno-18-2" name="__codelineno-18-2" href="#__codelineno-18-2"></a><span class="w"> </span><span class="nt">&quot;id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;550e8400-e29b-41d4-a716-446655440000&quot;</span><span class="p">,</span>
</span><span id="__span-18-3"><a id="__codelineno-18-3" name="__codelineno-18-3" href="#__codelineno-18-3"></a><span class="w"> </span><span class="nt">&quot;isValid&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
</span><span id="__span-18-4"><a id="__codelineno-18-4" name="__codelineno-18-4" href="#__codelineno-18-4"></a><span class="w"> </span><span class="nt">&quot;lastValidated&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2026-02-13T10:40:00Z&quot;</span><span class="p">,</span>
</span><span id="__span-18-5"><a id="__codelineno-18-5" name="__codelineno-18-5" href="#__codelineno-18-5"></a><span class="w"> </span><span class="nt">&quot;metadata&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-18-6"><a id="__codelineno-18-6" name="__codelineno-18-6" href="#__codelineno-18-6"></a><span class="w"> </span><span class="nt">&quot;durationSeconds&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">180</span><span class="p">,</span>
</span><span id="__span-18-7"><a id="__codelineno-18-7" name="__codelineno-18-7" href="#__codelineno-18-7"></a><span class="w"> </span><span class="nt">&quot;width&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1920</span><span class="p">,</span>
</span><span id="__span-18-8"><a id="__codelineno-18-8" name="__codelineno-18-8" href="#__codelineno-18-8"></a><span class="w"> </span><span class="nt">&quot;height&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1080</span><span class="p">,</span>
</span><span id="__span-18-9"><a id="__codelineno-18-9" name="__codelineno-18-9" href="#__codelineno-18-9"></a><span class="w"> </span><span class="nt">&quot;quality&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;FHD&quot;</span><span class="p">,</span>
</span><span id="__span-18-10"><a id="__codelineno-18-10" name="__codelineno-18-10" href="#__codelineno-18-10"></a><span class="w"> </span><span class="nt">&quot;orientation&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;landscape&quot;</span><span class="p">,</span>
</span><span id="__span-18-11"><a id="__codelineno-18-11" name="__codelineno-18-11" href="#__codelineno-18-11"></a><span class="w"> </span><span class="nt">&quot;hasAudio&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span>
</span><span id="__span-18-12"><a id="__codelineno-18-12" name="__codelineno-18-12" href="#__codelineno-18-12"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-18-13"><a id="__codelineno-18-13" name="__codelineno-18-13" href="#__codelineno-18-13"></a><span class="p">}</span>
</span></code></pre></div>
<hr />
<h2 id="configuration">Configuration<a class="headerlink" href="#configuration" title="Permanent link">&para;</a></h2>
<h3 id="environment-variables">Environment Variables<a class="headerlink" href="#environment-variables" title="Permanent link">&para;</a></h3>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-19-1"><a id="__codelineno-19-1" name="__codelineno-19-1" href="#__codelineno-19-1"></a><span class="c1"># Media API Server</span>
</span><span id="__span-19-2"><a id="__codelineno-19-2" name="__codelineno-19-2" href="#__codelineno-19-2"></a><span class="nv">MEDIA_API_PORT</span><span class="o">=</span><span class="m">4100</span>
</span><span id="__span-19-3"><a id="__codelineno-19-3" name="__codelineno-19-3" href="#__codelineno-19-3"></a><span class="nv">MEDIA_API_HOST</span><span class="o">=</span><span class="m">0</span>.0.0.0
</span><span id="__span-19-4"><a id="__codelineno-19-4" name="__codelineno-19-4" href="#__codelineno-19-4"></a>
</span><span id="__span-19-5"><a id="__codelineno-19-5" name="__codelineno-19-5" href="#__codelineno-19-5"></a><span class="c1"># File Paths</span>
</span><span id="__span-19-6"><a id="__codelineno-19-6" name="__codelineno-19-6" href="#__codelineno-19-6"></a><span class="nv">MEDIA_LIBRARY_PATH</span><span class="o">=</span>/media/local/library
</span><span id="__span-19-7"><a id="__codelineno-19-7" name="__codelineno-19-7" href="#__codelineno-19-7"></a><span class="nv">MEDIA_INBOX_PATH</span><span class="o">=</span>/media/local/inbox
</span><span id="__span-19-8"><a id="__codelineno-19-8" name="__codelineno-19-8" href="#__codelineno-19-8"></a>
</span><span id="__span-19-9"><a id="__codelineno-19-9" name="__codelineno-19-9" href="#__codelineno-19-9"></a><span class="c1"># Feature Flags</span>
</span><span id="__span-19-10"><a id="__codelineno-19-10" name="__codelineno-19-10" href="#__codelineno-19-10"></a><span class="nv">ENABLE_MEDIA_FEATURES</span><span class="o">=</span><span class="nb">true</span>
</span><span id="__span-19-11"><a id="__codelineno-19-11" name="__codelineno-19-11" href="#__codelineno-19-11"></a>
</span><span id="__span-19-12"><a id="__codelineno-19-12" name="__codelineno-19-12" href="#__codelineno-19-12"></a><span class="c1"># Database (shared with main API)</span>
</span><span id="__span-19-13"><a id="__codelineno-19-13" name="__codelineno-19-13" href="#__codelineno-19-13"></a><span class="nv">DATABASE_URL</span><span class="o">=</span>postgresql://user:pass@v2-postgres:5432/v2_changemaker
</span><span id="__span-19-14"><a id="__codelineno-19-14" name="__codelineno-19-14" href="#__codelineno-19-14"></a>
</span><span id="__span-19-15"><a id="__codelineno-19-15" name="__codelineno-19-15" href="#__codelineno-19-15"></a><span class="c1"># FFprobe</span>
</span><span id="__span-19-16"><a id="__codelineno-19-16" name="__codelineno-19-16" href="#__codelineno-19-16"></a><span class="nv">FFPROBE_TIMEOUT</span><span class="o">=</span><span class="m">30000</span><span class="w"> </span><span class="c1"># milliseconds</span>
</span><span id="__span-19-17"><a id="__codelineno-19-17" name="__codelineno-19-17" href="#__codelineno-19-17"></a><span class="nv">FFPROBE_PATH</span><span class="o">=</span>/usr/bin/ffprobe<span class="w"> </span><span class="c1"># Auto-detected if not set</span>
</span></code></pre></div>
<h3 id="docker-volume-mounts">Docker Volume Mounts<a class="headerlink" href="#docker-volume-mounts" title="Permanent link">&para;</a></h3>
<div class="language-yaml highlight"><pre><span></span><code><span id="__span-20-1"><a id="__codelineno-20-1" name="__codelineno-20-1" href="#__codelineno-20-1"></a><span class="c1"># docker-compose.yml</span>
</span><span id="__span-20-2"><a id="__codelineno-20-2" name="__codelineno-20-2" href="#__codelineno-20-2"></a><span class="nt">services</span><span class="p">:</span>
</span><span id="__span-20-3"><a id="__codelineno-20-3" name="__codelineno-20-3" href="#__codelineno-20-3"></a><span class="w"> </span><span class="nt">media-api</span><span class="p">:</span>
</span><span id="__span-20-4"><a id="__codelineno-20-4" name="__codelineno-20-4" href="#__codelineno-20-4"></a><span class="w"> </span><span class="nt">volumes</span><span class="p">:</span>
</span><span id="__span-20-5"><a id="__codelineno-20-5" name="__codelineno-20-5" href="#__codelineno-20-5"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/media/local/library:/media/local/library:ro</span><span class="w"> </span><span class="c1"># Read-only library</span>
</span><span id="__span-20-6"><a id="__codelineno-20-6" name="__codelineno-20-6" href="#__codelineno-20-6"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/media/local/inbox:/media/local/inbox:rw</span><span class="w"> </span><span class="c1"># Read-write inbox</span>
</span></code></pre></div>
<p><strong>Important:</strong> Inbox requires <code>:rw</code> (read-write) for uploads. Library can be <code>:ro</code> (read-only) for security.</p>
<h3 id="site-settings">Site Settings<a class="headerlink" href="#site-settings" title="Permanent link">&para;</a></h3>
<p>The media system respects the global <code>ENABLE_MEDIA_FEATURES</code> flag in Site Settings:</p>
<div class="language-sql highlight"><pre><span></span><code><span id="__span-21-1"><a id="__codelineno-21-1" name="__codelineno-21-1" href="#__codelineno-21-1"></a><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">settings</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="k">key</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;ENABLE_MEDIA_FEATURES&#39;</span><span class="p">;</span>
</span></code></pre></div>
<p>When disabled:</p>
<ul>
<li>Media API still runs but returns 503 Service Unavailable</li>
<li>Admin GUI hides Media menu items</li>
<li>Public gallery shows maintenance message</li>
</ul>
<hr />
<h2 id="admin-workflow">Admin Workflow<a class="headerlink" href="#admin-workflow" title="Permanent link">&para;</a></h2>
<h3 id="viewing-the-video-library">Viewing the Video Library<a class="headerlink" href="#viewing-the-video-library" title="Permanent link">&para;</a></h3>
<ol>
<li>Navigate to <strong>Media → Library</strong> in admin sidebar</li>
<li>Table displays all videos with:</li>
<li>Thumbnail preview</li>
<li>Title, producer, creator</li>
<li>Duration, quality, orientation</li>
<li>Directory type</li>
<li>File size</li>
<li>Created date</li>
<li>Use filters at top:</li>
<li><strong>Directory Type</strong> dropdown</li>
<li><strong>Orientation</strong> radio buttons (All / Portrait / Landscape / Square)</li>
<li><strong>Quality</strong> checkboxes (SD, HD, FHD, UHD)</li>
<li><strong>Search</strong> input (searches title, producer, creator)</li>
</ol>
<h3 id="scanning-a-directory">Scanning a Directory<a class="headerlink" href="#scanning-a-directory" title="Permanent link">&para;</a></h3>
<p><strong>When to Use:</strong></p>
<ul>
<li>After manually copying videos to library directory</li>
<li>After video processing jobs complete</li>
<li>When videos exist on filesystem but not in database</li>
</ul>
<p><strong>Steps:</strong></p>
<ol>
<li>Click <strong>"Scan Directory"</strong> button in Library page toolbar</li>
<li>Select directory type from dropdown</li>
<li>Toggle <strong>"Skip Existing"</strong> (recommended for large libraries)</li>
<li>Click <strong>"Start Scan"</strong></li>
<li>Progress modal shows:</li>
<li>Files scanned</li>
<li>New records created</li>
<li>Skipped (already in DB)</li>
<li>Failed (with error messages)</li>
<li>Click <strong>"Close"</strong> when complete</li>
<li>Table refreshes with new videos</li>
</ol>
<p><strong>Example Output:</strong></p>
<div class="language-text highlight"><pre><span></span><code><span id="__span-22-1"><a id="__codelineno-22-1" name="__codelineno-22-1" href="#__codelineno-22-1"></a>Scanning /media/local/library/videos...
</span><span id="__span-22-2"><a id="__codelineno-22-2" name="__codelineno-22-2" href="#__codelineno-22-2"></a>Found 45 video files
</span><span id="__span-22-3"><a id="__codelineno-22-3" name="__codelineno-22-3" href="#__codelineno-22-3"></a>- Created 12 new records
</span><span id="__span-22-4"><a id="__codelineno-22-4" name="__codelineno-22-4" href="#__codelineno-22-4"></a>- Skipped 33 existing records
</span><span id="__span-22-5"><a id="__codelineno-22-5" name="__codelineno-22-5" href="#__codelineno-22-5"></a>- Failed 0 files
</span><span id="__span-22-6"><a id="__codelineno-22-6" name="__codelineno-22-6" href="#__codelineno-22-6"></a>Scan complete in 8.3 seconds
</span></code></pre></div>
<h3 id="editing-video-metadata">Editing Video Metadata<a class="headerlink" href="#editing-video-metadata" title="Permanent link">&para;</a></h3>
<ol>
<li>Click <strong>pencil icon</strong> in video row</li>
<li>Edit modal opens with fields:</li>
<li><strong>Producer</strong> — Studio or production company</li>
<li><strong>Creator</strong> — Director or primary creator</li>
<li><strong>Title</strong> — Display title</li>
<li><strong>Tags</strong> — Comma-separated tags (auto-suggests existing tags)</li>
<li>Click <strong>"Save"</strong> to update</li>
<li>Metadata changes immediately visible in table</li>
</ol>
<p><strong>Bulk Editing:</strong></p>
<ol>
<li>Select multiple videos using checkboxes</li>
<li>Click <strong>"Bulk Edit"</strong> button</li>
<li>Set common fields (producer, tags, etc.)</li>
<li>Click <strong>"Apply to Selected"</strong></li>
</ol>
<h3 id="validating-videos">Validating Videos<a class="headerlink" href="#validating-videos" title="Permanent link">&para;</a></h3>
<p><strong>Purpose:</strong> Refresh metadata and verify file integrity</p>
<p><strong>Steps:</strong></p>
<ol>
<li>Click <strong>"Validate"</strong> button in video row (or Actions dropdown)</li>
<li>FFprobe re-analyzes video file</li>
<li>Database updates with fresh metadata:</li>
<li>Duration (may have changed if file was re-encoded)</li>
<li>Dimensions</li>
<li>Audio detection</li>
<li>File size and hash</li>
<li><code>lastValidated</code> timestamp updates</li>
<li>If file missing or corrupt, <code>isValid</code> set to <code>false</code></li>
</ol>
<p><strong>Bulk Validation:</strong></p>
<ol>
<li>Select multiple videos</li>
<li>Click <strong>"Validate Selected"</strong></li>
<li>Progress modal shows validation results</li>
<li>Failed validations highlighted in red</li>
</ol>
<h3 id="deleting-videos">Deleting Videos<a class="headerlink" href="#deleting-videos" title="Permanent link">&para;</a></h3>
<p><strong>Soft Delete (Default):</strong></p>
<ol>
<li>Click <strong>trash icon</strong> in video row</li>
<li>Confirm deletion dialog</li>
<li>Video marked <code>isValid = false</code></li>
<li>Video disappears from default view</li>
<li>File remains on filesystem</li>
<li>Record preserved in database</li>
</ol>
<p><strong>Viewing Deleted Videos:</strong></p>
<ol>
<li>Toggle <strong>"Show Invalid"</strong> filter</li>
<li>Deleted videos appear with strikethrough</li>
<li>Can restore by clicking <strong>"Restore"</strong> button</li>
</ol>
<p><strong>Hard Delete (Database Only):</strong></p>
<ol>
<li>Filter for invalid videos</li>
<li>Select video(s)</li>
<li>Click <strong>"Permanently Delete"</strong></li>
<li>Removes database record</li>
<li>File still on filesystem (manual cleanup required)</li>
</ol>
<p><strong>File System Cleanup:</strong></p>
<p>Deleted video files must be manually removed from filesystem:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-23-1"><a id="__codelineno-23-1" name="__codelineno-23-1" href="#__codelineno-23-1"></a><span class="c1"># SSH into media-api container</span>
</span><span id="__span-23-2"><a id="__codelineno-23-2" name="__codelineno-23-2" href="#__codelineno-23-2"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>media-api<span class="w"> </span>sh
</span><span id="__span-23-3"><a id="__codelineno-23-3" name="__codelineno-23-3" href="#__codelineno-23-3"></a>
</span><span id="__span-23-4"><a id="__codelineno-23-4" name="__codelineno-23-4" href="#__codelineno-23-4"></a><span class="c1"># Navigate to library</span>
</span><span id="__span-23-5"><a id="__codelineno-23-5" name="__codelineno-23-5" href="#__codelineno-23-5"></a><span class="nb">cd</span><span class="w"> </span>/media/local/library/videos
</span><span id="__span-23-6"><a id="__codelineno-23-6" name="__codelineno-23-6" href="#__codelineno-23-6"></a>
</span><span id="__span-23-7"><a id="__codelineno-23-7" name="__codelineno-23-7" href="#__codelineno-23-7"></a><span class="c1"># Remove specific file</span>
</span><span id="__span-23-8"><a id="__codelineno-23-8" name="__codelineno-23-8" href="#__codelineno-23-8"></a>rm<span class="w"> </span>deleted-video.mp4
</span><span id="__span-23-9"><a id="__codelineno-23-9" name="__codelineno-23-9" href="#__codelineno-23-9"></a>
</span><span id="__span-23-10"><a id="__codelineno-23-10" name="__codelineno-23-10" href="#__codelineno-23-10"></a><span class="c1"># Or find and remove all invalid videos (BE CAREFUL)</span>
</span><span id="__span-23-11"><a id="__codelineno-23-11" name="__codelineno-23-11" href="#__codelineno-23-11"></a><span class="c1"># (requires database query to get invalid file paths)</span>
</span></code></pre></div>
<hr />
<h2 id="directory-structure">Directory Structure<a class="headerlink" href="#directory-structure" title="Permanent link">&para;</a></h2>
<div class="language-text highlight"><pre><span></span><code><span id="__span-24-1"><a id="__codelineno-24-1" name="__codelineno-24-1" href="#__codelineno-24-1"></a>/media/local/library/
</span><span id="__span-24-2"><a id="__codelineno-24-2" name="__codelineno-24-2" href="#__codelineno-24-2"></a>├── studios/ # Studio-organized content
</span><span id="__span-24-3"><a id="__codelineno-24-3" name="__codelineno-24-3" href="#__codelineno-24-3"></a>│ ├── studio-a/
</span><span id="__span-24-4"><a id="__codelineno-24-4" name="__codelineno-24-4" href="#__codelineno-24-4"></a>│ │ ├── video-001.mp4
</span><span id="__span-24-5"><a id="__codelineno-24-5" name="__codelineno-24-5" href="#__codelineno-24-5"></a>│ │ └── video-002.mp4
</span><span id="__span-24-6"><a id="__codelineno-24-6" name="__codelineno-24-6" href="#__codelineno-24-6"></a>│ └── studio-b/
</span><span id="__span-24-7"><a id="__codelineno-24-7" name="__codelineno-24-7" href="#__codelineno-24-7"></a>│ └── video-003.mp4
</span><span id="__span-24-8"><a id="__codelineno-24-8" name="__codelineno-24-8" href="#__codelineno-24-8"></a>
</span><span id="__span-24-9"><a id="__codelineno-24-9" name="__codelineno-24-9" href="#__codelineno-24-9"></a>├── gifs/ # Short looping videos
</span><span id="__span-24-10"><a id="__codelineno-24-10" name="__codelineno-24-10" href="#__codelineno-24-10"></a>│ ├── loop-001.mp4
</span><span id="__span-24-11"><a id="__codelineno-24-11" name="__codelineno-24-11" href="#__codelineno-24-11"></a>│ └── loop-002.webm
</span><span id="__span-24-12"><a id="__codelineno-24-12" name="__codelineno-24-12" href="#__codelineno-24-12"></a>
</span><span id="__span-24-13"><a id="__codelineno-24-13" name="__codelineno-24-13" href="#__codelineno-24-13"></a>├── private/ # Private/unreleased content
</span><span id="__span-24-14"><a id="__codelineno-24-14" name="__codelineno-24-14" href="#__codelineno-24-14"></a>│ └── unreleased.mp4
</span><span id="__span-24-15"><a id="__codelineno-24-15" name="__codelineno-24-15" href="#__codelineno-24-15"></a>
</span><span id="__span-24-16"><a id="__codelineno-24-16" name="__codelineno-24-16" href="#__codelineno-24-16"></a>├── inbox/ # Upload staging area (READ-WRITE)
</span><span id="__span-24-17"><a id="__codelineno-24-17" name="__codelineno-24-17" href="#__codelineno-24-17"></a>│ ├── uuid-123.mp4 # Temp uploads
</span><span id="__span-24-18"><a id="__codelineno-24-18" name="__codelineno-24-18" href="#__codelineno-24-18"></a>│ └── uuid-456.mov
</span><span id="__span-24-19"><a id="__codelineno-24-19" name="__codelineno-24-19" href="#__codelineno-24-19"></a>
</span><span id="__span-24-20"><a id="__codelineno-24-20" name="__codelineno-24-20" href="#__codelineno-24-20"></a>├── curated/ # Hand-picked highlights
</span><span id="__span-24-21"><a id="__codelineno-24-21" name="__codelineno-24-21" href="#__codelineno-24-21"></a>│ ├── best-of-2025.mp4
</span><span id="__span-24-22"><a id="__codelineno-24-22" name="__codelineno-24-22" href="#__codelineno-24-22"></a>│ └── top-plays.mp4
</span><span id="__span-24-23"><a id="__codelineno-24-23" name="__codelineno-24-23" href="#__codelineno-24-23"></a>
</span><span id="__span-24-24"><a id="__codelineno-24-24" name="__codelineno-24-24" href="#__codelineno-24-24"></a>├── playback/ # Playback-optimized encodes
</span><span id="__span-24-25"><a id="__codelineno-24-25" name="__codelineno-24-25" href="#__codelineno-24-25"></a>│ ├── streaming-001.mp4
</span><span id="__span-24-26"><a id="__codelineno-24-26" name="__codelineno-24-26" href="#__codelineno-24-26"></a>│ └── streaming-002.mp4
</span><span id="__span-24-27"><a id="__codelineno-24-27" name="__codelineno-24-27" href="#__codelineno-24-27"></a>
</span><span id="__span-24-28"><a id="__codelineno-24-28" name="__codelineno-24-28" href="#__codelineno-24-28"></a>├── compilations/ # Multi-video compilations
</span><span id="__span-24-29"><a id="__codelineno-24-29" name="__codelineno-24-29" href="#__codelineno-24-29"></a>│ ├── compilation-001.mp4
</span><span id="__span-24-30"><a id="__codelineno-24-30" name="__codelineno-24-30" href="#__codelineno-24-30"></a>│ └── mega-compilation.mp4
</span><span id="__span-24-31"><a id="__codelineno-24-31" name="__codelineno-24-31" href="#__codelineno-24-31"></a>
</span><span id="__span-24-32"><a id="__codelineno-24-32" name="__codelineno-24-32" href="#__codelineno-24-32"></a>├── videos/ # General video library
</span><span id="__span-24-33"><a id="__codelineno-24-33" name="__codelineno-24-33" href="#__codelineno-24-33"></a>│ ├── video-001.mp4
</span><span id="__span-24-34"><a id="__codelineno-24-34" name="__codelineno-24-34" href="#__codelineno-24-34"></a>│ ├── video-002.mp4
</span><span id="__span-24-35"><a id="__codelineno-24-35" name="__codelineno-24-35" href="#__codelineno-24-35"></a>│ └── ... (thousands of videos)
</span><span id="__span-24-36"><a id="__codelineno-24-36" name="__codelineno-24-36" href="#__codelineno-24-36"></a>
</span><span id="__span-24-37"><a id="__codelineno-24-37" name="__codelineno-24-37" href="#__codelineno-24-37"></a>└── highlights/ # Auto-generated highlights
</span><span id="__span-24-38"><a id="__codelineno-24-38" name="__codelineno-24-38" href="#__codelineno-24-38"></a> ├── highlight-001.mp4
</span><span id="__span-24-39"><a id="__codelineno-24-39" name="__codelineno-24-39" href="#__codelineno-24-39"></a> └── highlight-002.mp4
</span></code></pre></div>
<p><strong>Directory Guidelines:</strong></p>
<ul>
<li><strong>studios/</strong> — Organize by producer/studio name (subfolder structure allowed)</li>
<li><strong>gifs/</strong> — Short videos under 15 seconds, suitable for looping</li>
<li><strong>private/</strong> — Never shared publicly, admin-only access</li>
<li><strong>inbox/</strong> — Temporary upload location, files moved after processing</li>
<li><strong>curated/</strong> — High-quality selections for public gallery homepage</li>
<li><strong>playback/</strong> — Web-optimized encodes (H.264, web-friendly profiles)</li>
<li><strong>compilations/</strong> — Merged videos created by compilation jobs</li>
<li><strong>videos/</strong> — Main library, all-purpose storage</li>
<li><strong>highlights/</strong> — AI-generated or manually created highlight reels</li>
</ul>
<hr />
<h2 id="code-examples">Code Examples<a class="headerlink" href="#code-examples" title="Permanent link">&para;</a></h2>
<h3 id="list-videos-with-filters-fastify-route">List Videos with Filters (Fastify Route)<a class="headerlink" href="#list-videos-with-filters-fastify-route" title="Permanent link">&para;</a></h3>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-25-1"><a id="__codelineno-25-1" name="__codelineno-25-1" href="#__codelineno-25-1"></a><span class="c1">// api/src/modules/media/routes/videos.routes.ts</span>
</span><span id="__span-25-2"><a id="__codelineno-25-2" name="__codelineno-25-2" href="#__codelineno-25-2"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">FastifyInstance</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;fastify&#39;</span><span class="p">;</span>
</span><span id="__span-25-3"><a id="__codelineno-25-3" name="__codelineno-25-3" href="#__codelineno-25-3"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">eq</span><span class="p">,</span><span class="w"> </span><span class="nx">and</span><span class="p">,</span><span class="w"> </span><span class="nx">like</span><span class="p">,</span><span class="w"> </span><span class="nx">desc</span><span class="p">,</span><span class="w"> </span><span class="nx">sql</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;drizzle-orm&#39;</span><span class="p">;</span>
</span><span id="__span-25-4"><a id="__codelineno-25-4" name="__codelineno-25-4" href="#__codelineno-25-4"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">videos</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;@/modules/media/db/schema&#39;</span><span class="p">;</span>
</span><span id="__span-25-5"><a id="__codelineno-25-5" name="__codelineno-25-5" href="#__codelineno-25-5"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">db</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;@/modules/media/db&#39;</span><span class="p">;</span>
</span><span id="__span-25-6"><a id="__codelineno-25-6" name="__codelineno-25-6" href="#__codelineno-25-6"></a>
</span><span id="__span-25-7"><a id="__codelineno-25-7" name="__codelineno-25-7" href="#__codelineno-25-7"></a><span class="k">export</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="p">(</span><span class="nx">app</span><span class="o">:</span><span class="w"> </span><span class="kt">FastifyInstance</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-25-8"><a id="__codelineno-25-8" name="__codelineno-25-8" href="#__codelineno-25-8"></a><span class="w"> </span><span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;/api/media/videos&#39;</span><span class="p">,</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="p">(</span><span class="nx">req</span><span class="p">,</span><span class="w"> </span><span class="nx">reply</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-25-9"><a id="__codelineno-25-9" name="__codelineno-25-9" href="#__codelineno-25-9"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-25-10"><a id="__codelineno-25-10" name="__codelineno-25-10" href="#__codelineno-25-10"></a><span class="w"> </span><span class="nx">page</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">1</span><span class="p">,</span>
</span><span id="__span-25-11"><a id="__codelineno-25-11" name="__codelineno-25-11" href="#__codelineno-25-11"></a><span class="w"> </span><span class="nx">limit</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">20</span><span class="p">,</span>
</span><span id="__span-25-12"><a id="__codelineno-25-12" name="__codelineno-25-12" href="#__codelineno-25-12"></a><span class="w"> </span><span class="nx">directoryType</span><span class="p">,</span>
</span><span id="__span-25-13"><a id="__codelineno-25-13" name="__codelineno-25-13" href="#__codelineno-25-13"></a><span class="w"> </span><span class="nx">orientation</span><span class="p">,</span>
</span><span id="__span-25-14"><a id="__codelineno-25-14" name="__codelineno-25-14" href="#__codelineno-25-14"></a><span class="w"> </span><span class="nx">producer</span><span class="p">,</span>
</span><span id="__span-25-15"><a id="__codelineno-25-15" name="__codelineno-25-15" href="#__codelineno-25-15"></a><span class="w"> </span><span class="nx">creator</span><span class="p">,</span>
</span><span id="__span-25-16"><a id="__codelineno-25-16" name="__codelineno-25-16" href="#__codelineno-25-16"></a><span class="w"> </span><span class="nx">quality</span><span class="p">,</span>
</span><span id="__span-25-17"><a id="__codelineno-25-17" name="__codelineno-25-17" href="#__codelineno-25-17"></a><span class="w"> </span><span class="nx">hasAudio</span><span class="p">,</span>
</span><span id="__span-25-18"><a id="__codelineno-25-18" name="__codelineno-25-18" href="#__codelineno-25-18"></a><span class="w"> </span><span class="nx">isValid</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
</span><span id="__span-25-19"><a id="__codelineno-25-19" name="__codelineno-25-19" href="#__codelineno-25-19"></a><span class="w"> </span><span class="nx">search</span><span class="p">,</span>
</span><span id="__span-25-20"><a id="__codelineno-25-20" name="__codelineno-25-20" href="#__codelineno-25-20"></a><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">query</span><span class="w"> </span><span class="kr">as</span><span class="w"> </span><span class="nx">any</span><span class="p">;</span>
</span><span id="__span-25-21"><a id="__codelineno-25-21" name="__codelineno-25-21" href="#__codelineno-25-21"></a>
</span><span id="__span-25-22"><a id="__codelineno-25-22" name="__codelineno-25-22" href="#__codelineno-25-22"></a><span class="w"> </span><span class="c1">// Build filters</span>
</span><span id="__span-25-23"><a id="__codelineno-25-23" name="__codelineno-25-23" href="#__codelineno-25-23"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">filters</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[];</span>
</span><span id="__span-25-24"><a id="__codelineno-25-24" name="__codelineno-25-24" href="#__codelineno-25-24"></a>
</span><span id="__span-25-25"><a id="__codelineno-25-25" name="__codelineno-25-25" href="#__codelineno-25-25"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">directoryType</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-25-26"><a id="__codelineno-25-26" name="__codelineno-25-26" href="#__codelineno-25-26"></a><span class="w"> </span><span class="nx">filters</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">eq</span><span class="p">(</span><span class="nx">videos</span><span class="p">.</span><span class="nx">directoryType</span><span class="p">,</span><span class="w"> </span><span class="nx">directoryType</span><span class="p">));</span>
</span><span id="__span-25-27"><a id="__codelineno-25-27" name="__codelineno-25-27" href="#__codelineno-25-27"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-25-28"><a id="__codelineno-25-28" name="__codelineno-25-28" href="#__codelineno-25-28"></a>
</span><span id="__span-25-29"><a id="__codelineno-25-29" name="__codelineno-25-29" href="#__codelineno-25-29"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">orientation</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-25-30"><a id="__codelineno-25-30" name="__codelineno-25-30" href="#__codelineno-25-30"></a><span class="w"> </span><span class="nx">filters</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">eq</span><span class="p">(</span><span class="nx">videos</span><span class="p">.</span><span class="nx">orientation</span><span class="p">,</span><span class="w"> </span><span class="nx">orientation</span><span class="p">));</span>
</span><span id="__span-25-31"><a id="__codelineno-25-31" name="__codelineno-25-31" href="#__codelineno-25-31"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-25-32"><a id="__codelineno-25-32" name="__codelineno-25-32" href="#__codelineno-25-32"></a>
</span><span id="__span-25-33"><a id="__codelineno-25-33" name="__codelineno-25-33" href="#__codelineno-25-33"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">producer</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-25-34"><a id="__codelineno-25-34" name="__codelineno-25-34" href="#__codelineno-25-34"></a><span class="w"> </span><span class="nx">filters</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">like</span><span class="p">(</span><span class="nx">videos</span><span class="p">.</span><span class="nx">producer</span><span class="p">,</span><span class="w"> </span><span class="sb">`%</span><span class="si">${</span><span class="nx">producer</span><span class="si">}</span><span class="sb">%`</span><span class="p">));</span>
</span><span id="__span-25-35"><a id="__codelineno-25-35" name="__codelineno-25-35" href="#__codelineno-25-35"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-25-36"><a id="__codelineno-25-36" name="__codelineno-25-36" href="#__codelineno-25-36"></a>
</span><span id="__span-25-37"><a id="__codelineno-25-37" name="__codelineno-25-37" href="#__codelineno-25-37"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">creator</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-25-38"><a id="__codelineno-25-38" name="__codelineno-25-38" href="#__codelineno-25-38"></a><span class="w"> </span><span class="nx">filters</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">like</span><span class="p">(</span><span class="nx">videos</span><span class="p">.</span><span class="nx">creator</span><span class="p">,</span><span class="w"> </span><span class="sb">`%</span><span class="si">${</span><span class="nx">creator</span><span class="si">}</span><span class="sb">%`</span><span class="p">));</span>
</span><span id="__span-25-39"><a id="__codelineno-25-39" name="__codelineno-25-39" href="#__codelineno-25-39"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-25-40"><a id="__codelineno-25-40" name="__codelineno-25-40" href="#__codelineno-25-40"></a>
</span><span id="__span-25-41"><a id="__codelineno-25-41" name="__codelineno-25-41" href="#__codelineno-25-41"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">quality</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-25-42"><a id="__codelineno-25-42" name="__codelineno-25-42" href="#__codelineno-25-42"></a><span class="w"> </span><span class="nx">filters</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">eq</span><span class="p">(</span><span class="nx">videos</span><span class="p">.</span><span class="nx">quality</span><span class="p">,</span><span class="w"> </span><span class="nx">quality</span><span class="p">));</span>
</span><span id="__span-25-43"><a id="__codelineno-25-43" name="__codelineno-25-43" href="#__codelineno-25-43"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-25-44"><a id="__codelineno-25-44" name="__codelineno-25-44" href="#__codelineno-25-44"></a>
</span><span id="__span-25-45"><a id="__codelineno-25-45" name="__codelineno-25-45" href="#__codelineno-25-45"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="ow">typeof</span><span class="w"> </span><span class="nx">hasAudio</span><span class="w"> </span><span class="o">===</span><span class="w"> </span><span class="s1">&#39;boolean&#39;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-25-46"><a id="__codelineno-25-46" name="__codelineno-25-46" href="#__codelineno-25-46"></a><span class="w"> </span><span class="nx">filters</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">eq</span><span class="p">(</span><span class="nx">videos</span><span class="p">.</span><span class="nx">hasAudio</span><span class="p">,</span><span class="w"> </span><span class="nx">hasAudio</span><span class="p">));</span>
</span><span id="__span-25-47"><a id="__codelineno-25-47" name="__codelineno-25-47" href="#__codelineno-25-47"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-25-48"><a id="__codelineno-25-48" name="__codelineno-25-48" href="#__codelineno-25-48"></a>
</span><span id="__span-25-49"><a id="__codelineno-25-49" name="__codelineno-25-49" href="#__codelineno-25-49"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="ow">typeof</span><span class="w"> </span><span class="nx">isValid</span><span class="w"> </span><span class="o">===</span><span class="w"> </span><span class="s1">&#39;boolean&#39;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-25-50"><a id="__codelineno-25-50" name="__codelineno-25-50" href="#__codelineno-25-50"></a><span class="w"> </span><span class="nx">filters</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">eq</span><span class="p">(</span><span class="nx">videos</span><span class="p">.</span><span class="nx">isValid</span><span class="p">,</span><span class="w"> </span><span class="nx">isValid</span><span class="p">));</span>
</span><span id="__span-25-51"><a id="__codelineno-25-51" name="__codelineno-25-51" href="#__codelineno-25-51"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-25-52"><a id="__codelineno-25-52" name="__codelineno-25-52" href="#__codelineno-25-52"></a>
</span><span id="__span-25-53"><a id="__codelineno-25-53" name="__codelineno-25-53" href="#__codelineno-25-53"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">search</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-25-54"><a id="__codelineno-25-54" name="__codelineno-25-54" href="#__codelineno-25-54"></a><span class="w"> </span><span class="nx">filters</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span>
</span><span id="__span-25-55"><a id="__codelineno-25-55" name="__codelineno-25-55" href="#__codelineno-25-55"></a><span class="w"> </span><span class="nx">sql</span><span class="sb">`(</span>
</span><span id="__span-25-56"><a id="__codelineno-25-56" name="__codelineno-25-56" href="#__codelineno-25-56"></a><span class="sb"> </span><span class="si">${</span><span class="nx">videos</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span><span class="sb"> ILIKE </span><span class="si">${</span><span class="s1">&#39;%&#39;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">search</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s1">&#39;%&#39;</span><span class="si">}</span><span class="sb"> OR</span>
</span><span id="__span-25-57"><a id="__codelineno-25-57" name="__codelineno-25-57" href="#__codelineno-25-57"></a><span class="sb"> </span><span class="si">${</span><span class="nx">videos</span><span class="p">.</span><span class="nx">producer</span><span class="si">}</span><span class="sb"> ILIKE </span><span class="si">${</span><span class="s1">&#39;%&#39;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">search</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s1">&#39;%&#39;</span><span class="si">}</span><span class="sb"> OR</span>
</span><span id="__span-25-58"><a id="__codelineno-25-58" name="__codelineno-25-58" href="#__codelineno-25-58"></a><span class="sb"> </span><span class="si">${</span><span class="nx">videos</span><span class="p">.</span><span class="nx">creator</span><span class="si">}</span><span class="sb"> ILIKE </span><span class="si">${</span><span class="s1">&#39;%&#39;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">search</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s1">&#39;%&#39;</span><span class="si">}</span>
</span><span id="__span-25-59"><a id="__codelineno-25-59" name="__codelineno-25-59" href="#__codelineno-25-59"></a><span class="sb"> )`</span>
</span><span id="__span-25-60"><a id="__codelineno-25-60" name="__codelineno-25-60" href="#__codelineno-25-60"></a><span class="w"> </span><span class="p">);</span>
</span><span id="__span-25-61"><a id="__codelineno-25-61" name="__codelineno-25-61" href="#__codelineno-25-61"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-25-62"><a id="__codelineno-25-62" name="__codelineno-25-62" href="#__codelineno-25-62"></a>
</span><span id="__span-25-63"><a id="__codelineno-25-63" name="__codelineno-25-63" href="#__codelineno-25-63"></a><span class="w"> </span><span class="c1">// Count total</span>
</span><span id="__span-25-64"><a id="__codelineno-25-64" name="__codelineno-25-64" href="#__codelineno-25-64"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">[{</span><span class="w"> </span><span class="nx">count</span><span class="w"> </span><span class="p">}]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">db</span>
</span><span id="__span-25-65"><a id="__codelineno-25-65" name="__codelineno-25-65" href="#__codelineno-25-65"></a><span class="w"> </span><span class="p">.</span><span class="nx">select</span><span class="p">({</span><span class="w"> </span><span class="nx">count</span><span class="o">:</span><span class="w"> </span><span class="kt">sql</span><span class="o">&lt;</span><span class="kt">number</span><span class="o">&gt;</span><span class="sb">`count(*)`</span><span class="w"> </span><span class="p">})</span>
</span><span id="__span-25-66"><a id="__codelineno-25-66" name="__codelineno-25-66" href="#__codelineno-25-66"></a><span class="w"> </span><span class="p">.</span><span class="kr">from</span><span class="p">(</span><span class="nx">videos</span><span class="p">)</span>
</span><span id="__span-25-67"><a id="__codelineno-25-67" name="__codelineno-25-67" href="#__codelineno-25-67"></a><span class="w"> </span><span class="p">.</span><span class="nx">where</span><span class="p">(</span><span class="nx">and</span><span class="p">(...</span><span class="nx">filters</span><span class="p">));</span>
</span><span id="__span-25-68"><a id="__codelineno-25-68" name="__codelineno-25-68" href="#__codelineno-25-68"></a>
</span><span id="__span-25-69"><a id="__codelineno-25-69" name="__codelineno-25-69" href="#__codelineno-25-69"></a><span class="w"> </span><span class="c1">// Fetch paginated results</span>
</span><span id="__span-25-70"><a id="__codelineno-25-70" name="__codelineno-25-70" href="#__codelineno-25-70"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">results</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">db</span>
</span><span id="__span-25-71"><a id="__codelineno-25-71" name="__codelineno-25-71" href="#__codelineno-25-71"></a><span class="w"> </span><span class="p">.</span><span class="nx">select</span><span class="p">()</span>
</span><span id="__span-25-72"><a id="__codelineno-25-72" name="__codelineno-25-72" href="#__codelineno-25-72"></a><span class="w"> </span><span class="p">.</span><span class="kr">from</span><span class="p">(</span><span class="nx">videos</span><span class="p">)</span>
</span><span id="__span-25-73"><a id="__codelineno-25-73" name="__codelineno-25-73" href="#__codelineno-25-73"></a><span class="w"> </span><span class="p">.</span><span class="nx">where</span><span class="p">(</span><span class="nx">and</span><span class="p">(...</span><span class="nx">filters</span><span class="p">))</span>
</span><span id="__span-25-74"><a id="__codelineno-25-74" name="__codelineno-25-74" href="#__codelineno-25-74"></a><span class="w"> </span><span class="p">.</span><span class="nx">limit</span><span class="p">(</span><span class="nb">Number</span><span class="p">(</span><span class="nx">limit</span><span class="p">))</span>
</span><span id="__span-25-75"><a id="__codelineno-25-75" name="__codelineno-25-75" href="#__codelineno-25-75"></a><span class="w"> </span><span class="p">.</span><span class="nx">offset</span><span class="p">((</span><span class="nb">Number</span><span class="p">(</span><span class="nx">page</span><span class="p">)</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mf">1</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nb">Number</span><span class="p">(</span><span class="nx">limit</span><span class="p">))</span>
</span><span id="__span-25-76"><a id="__codelineno-25-76" name="__codelineno-25-76" href="#__codelineno-25-76"></a><span class="w"> </span><span class="p">.</span><span class="nx">orderBy</span><span class="p">(</span><span class="nx">desc</span><span class="p">(</span><span class="nx">videos</span><span class="p">.</span><span class="nx">createdAt</span><span class="p">));</span>
</span><span id="__span-25-77"><a id="__codelineno-25-77" name="__codelineno-25-77" href="#__codelineno-25-77"></a>
</span><span id="__span-25-78"><a id="__codelineno-25-78" name="__codelineno-25-78" href="#__codelineno-25-78"></a><span class="w"> </span><span class="nx">reply</span><span class="p">.</span><span class="nx">send</span><span class="p">({</span>
</span><span id="__span-25-79"><a id="__codelineno-25-79" name="__codelineno-25-79" href="#__codelineno-25-79"></a><span class="w"> </span><span class="nx">data</span><span class="o">:</span><span class="w"> </span><span class="kt">results</span><span class="p">,</span>
</span><span id="__span-25-80"><a id="__codelineno-25-80" name="__codelineno-25-80" href="#__codelineno-25-80"></a><span class="w"> </span><span class="nx">pagination</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-25-81"><a id="__codelineno-25-81" name="__codelineno-25-81" href="#__codelineno-25-81"></a><span class="w"> </span><span class="nx">page</span><span class="o">:</span><span class="w"> </span><span class="kt">Number</span><span class="p">(</span><span class="nx">page</span><span class="p">),</span>
</span><span id="__span-25-82"><a id="__codelineno-25-82" name="__codelineno-25-82" href="#__codelineno-25-82"></a><span class="w"> </span><span class="nx">limit</span><span class="o">:</span><span class="w"> </span><span class="kt">Number</span><span class="p">(</span><span class="nx">limit</span><span class="p">),</span>
</span><span id="__span-25-83"><a id="__codelineno-25-83" name="__codelineno-25-83" href="#__codelineno-25-83"></a><span class="w"> </span><span class="nx">total</span><span class="o">:</span><span class="w"> </span><span class="kt">Number</span><span class="p">(</span><span class="nx">count</span><span class="p">),</span>
</span><span id="__span-25-84"><a id="__codelineno-25-84" name="__codelineno-25-84" href="#__codelineno-25-84"></a><span class="w"> </span><span class="nx">totalPages</span><span class="o">:</span><span class="w"> </span><span class="kt">Math.ceil</span><span class="p">(</span><span class="nb">Number</span><span class="p">(</span><span class="nx">count</span><span class="p">)</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="nb">Number</span><span class="p">(</span><span class="nx">limit</span><span class="p">)),</span>
</span><span id="__span-25-85"><a id="__codelineno-25-85" name="__codelineno-25-85" href="#__codelineno-25-85"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-25-86"><a id="__codelineno-25-86" name="__codelineno-25-86" href="#__codelineno-25-86"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-25-87"><a id="__codelineno-25-87" name="__codelineno-25-87" href="#__codelineno-25-87"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-25-88"><a id="__codelineno-25-88" name="__codelineno-25-88" href="#__codelineno-25-88"></a><span class="p">}</span>
</span></code></pre></div>
<hr />
<h3 id="scan-directory-for-videos">Scan Directory for Videos<a class="headerlink" href="#scan-directory-for-videos" title="Permanent link">&para;</a></h3>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-26-1"><a id="__codelineno-26-1" name="__codelineno-26-1" href="#__codelineno-26-1"></a><span class="c1">// api/src/modules/media/routes/videos.routes.ts</span>
</span><span id="__span-26-2"><a id="__codelineno-26-2" name="__codelineno-26-2" href="#__codelineno-26-2"></a><span class="k">import</span><span class="w"> </span><span class="nx">fs</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;fs/promises&#39;</span><span class="p">;</span>
</span><span id="__span-26-3"><a id="__codelineno-26-3" name="__codelineno-26-3" href="#__codelineno-26-3"></a><span class="k">import</span><span class="w"> </span><span class="nx">path</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;path&#39;</span><span class="p">;</span>
</span><span id="__span-26-4"><a id="__codelineno-26-4" name="__codelineno-26-4" href="#__codelineno-26-4"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">eq</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;drizzle-orm&#39;</span><span class="p">;</span>
</span><span id="__span-26-5"><a id="__codelineno-26-5" name="__codelineno-26-5" href="#__codelineno-26-5"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">videos</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;@/modules/media/db/schema&#39;</span><span class="p">;</span>
</span><span id="__span-26-6"><a id="__codelineno-26-6" name="__codelineno-26-6" href="#__codelineno-26-6"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">ffprobeService</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;@/modules/media/services/ffprobe.service&#39;</span><span class="p">;</span>
</span><span id="__span-26-7"><a id="__codelineno-26-7" name="__codelineno-26-7" href="#__codelineno-26-7"></a>
</span><span id="__span-26-8"><a id="__codelineno-26-8" name="__codelineno-26-8" href="#__codelineno-26-8"></a><span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s1">&#39;/api/media/videos/scan&#39;</span><span class="p">,</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="p">(</span><span class="nx">req</span><span class="p">,</span><span class="w"> </span><span class="nx">reply</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-26-9"><a id="__codelineno-26-9" name="__codelineno-26-9" href="#__codelineno-26-9"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">directoryType</span><span class="p">,</span><span class="w"> </span><span class="nx">skipExisting</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="w"> </span><span class="kr">as</span><span class="w"> </span><span class="nx">any</span><span class="p">;</span>
</span><span id="__span-26-10"><a id="__codelineno-26-10" name="__codelineno-26-10" href="#__codelineno-26-10"></a>
</span><span id="__span-26-11"><a id="__codelineno-26-11" name="__codelineno-26-11" href="#__codelineno-26-11"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nx">directoryType</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-26-12"><a id="__codelineno-26-12" name="__codelineno-26-12" href="#__codelineno-26-12"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">reply</span><span class="p">.</span><span class="nx">code</span><span class="p">(</span><span class="mf">400</span><span class="p">).</span><span class="nx">send</span><span class="p">({</span><span class="w"> </span><span class="nx">error</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;directoryType required&#39;</span><span class="w"> </span><span class="p">});</span>
</span><span id="__span-26-13"><a id="__codelineno-26-13" name="__codelineno-26-13" href="#__codelineno-26-13"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-26-14"><a id="__codelineno-26-14" name="__codelineno-26-14" href="#__codelineno-26-14"></a>
</span><span id="__span-26-15"><a id="__codelineno-26-15" name="__codelineno-26-15" href="#__codelineno-26-15"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">dirPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">MEDIA_LIBRARY_PATH</span><span class="o">!</span><span class="p">,</span><span class="w"> </span><span class="nx">directoryType</span><span class="p">);</span>
</span><span id="__span-26-16"><a id="__codelineno-26-16" name="__codelineno-26-16" href="#__codelineno-26-16"></a>
</span><span id="__span-26-17"><a id="__codelineno-26-17" name="__codelineno-26-17" href="#__codelineno-26-17"></a><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-26-18"><a id="__codelineno-26-18" name="__codelineno-26-18" href="#__codelineno-26-18"></a><span class="w"> </span><span class="c1">// Check directory exists</span>
</span><span id="__span-26-19"><a id="__codelineno-26-19" name="__codelineno-26-19" href="#__codelineno-26-19"></a><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">fs</span><span class="p">.</span><span class="nx">access</span><span class="p">(</span><span class="nx">dirPath</span><span class="p">);</span>
</span><span id="__span-26-20"><a id="__codelineno-26-20" name="__codelineno-26-20" href="#__codelineno-26-20"></a><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-26-21"><a id="__codelineno-26-21" name="__codelineno-26-21" href="#__codelineno-26-21"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">reply</span><span class="p">.</span><span class="nx">code</span><span class="p">(</span><span class="mf">400</span><span class="p">).</span><span class="nx">send</span><span class="p">({</span><span class="w"> </span><span class="nx">error</span><span class="o">:</span><span class="w"> </span><span class="sb">`Directory not found: </span><span class="si">${</span><span class="nx">directoryType</span><span class="si">}</span><span class="sb">`</span><span class="w"> </span><span class="p">});</span>
</span><span id="__span-26-22"><a id="__codelineno-26-22" name="__codelineno-26-22" href="#__codelineno-26-22"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-26-23"><a id="__codelineno-26-23" name="__codelineno-26-23" href="#__codelineno-26-23"></a>
</span><span id="__span-26-24"><a id="__codelineno-26-24" name="__codelineno-26-24" href="#__codelineno-26-24"></a><span class="w"> </span><span class="c1">// Read directory</span>
</span><span id="__span-26-25"><a id="__codelineno-26-25" name="__codelineno-26-25" href="#__codelineno-26-25"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">files</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">fs</span><span class="p">.</span><span class="nx">readdir</span><span class="p">(</span><span class="nx">dirPath</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">recursive</span><span class="o">:</span><span class="w"> </span><span class="kt">true</span><span class="w"> </span><span class="p">});</span>
</span><span id="__span-26-26"><a id="__codelineno-26-26" name="__codelineno-26-26" href="#__codelineno-26-26"></a>
</span><span id="__span-26-27"><a id="__codelineno-26-27" name="__codelineno-26-27" href="#__codelineno-26-27"></a><span class="w"> </span><span class="c1">// Filter for video files</span>
</span><span id="__span-26-28"><a id="__codelineno-26-28" name="__codelineno-26-28" href="#__codelineno-26-28"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">videoExtensions</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s1">&#39;.mp4&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;.mov&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;.avi&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;.mkv&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;.webm&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;.m4v&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;.flv&#39;</span><span class="p">];</span>
</span><span id="__span-26-29"><a id="__codelineno-26-29" name="__codelineno-26-29" href="#__codelineno-26-29"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">videoFiles</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">files</span><span class="p">.</span><span class="nx">filter</span><span class="p">((</span><span class="nx">f</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span>
</span><span id="__span-26-30"><a id="__codelineno-26-30" name="__codelineno-26-30" href="#__codelineno-26-30"></a><span class="w"> </span><span class="nx">videoExtensions</span><span class="p">.</span><span class="nx">some</span><span class="p">((</span><span class="nx">ext</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">f</span><span class="p">.</span><span class="nx">toLowerCase</span><span class="p">().</span><span class="nx">endsWith</span><span class="p">(</span><span class="nx">ext</span><span class="p">))</span>
</span><span id="__span-26-31"><a id="__codelineno-26-31" name="__codelineno-26-31" href="#__codelineno-26-31"></a><span class="w"> </span><span class="p">);</span>
</span><span id="__span-26-32"><a id="__codelineno-26-32" name="__codelineno-26-32" href="#__codelineno-26-32"></a>
</span><span id="__span-26-33"><a id="__codelineno-26-33" name="__codelineno-26-33" href="#__codelineno-26-33"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">results</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-26-34"><a id="__codelineno-26-34" name="__codelineno-26-34" href="#__codelineno-26-34"></a><span class="w"> </span><span class="nx">scanned</span><span class="o">:</span><span class="w"> </span><span class="kt">videoFiles.length</span><span class="p">,</span>
</span><span id="__span-26-35"><a id="__codelineno-26-35" name="__codelineno-26-35" href="#__codelineno-26-35"></a><span class="w"> </span><span class="nx">created</span><span class="o">:</span><span class="w"> </span><span class="kt">0</span><span class="p">,</span>
</span><span id="__span-26-36"><a id="__codelineno-26-36" name="__codelineno-26-36" href="#__codelineno-26-36"></a><span class="w"> </span><span class="nx">skipped</span><span class="o">:</span><span class="w"> </span><span class="kt">0</span><span class="p">,</span>
</span><span id="__span-26-37"><a id="__codelineno-26-37" name="__codelineno-26-37" href="#__codelineno-26-37"></a><span class="w"> </span><span class="nx">failed</span><span class="o">:</span><span class="w"> </span><span class="kt">0</span><span class="p">,</span>
</span><span id="__span-26-38"><a id="__codelineno-26-38" name="__codelineno-26-38" href="#__codelineno-26-38"></a><span class="w"> </span><span class="nx">errors</span><span class="o">:</span><span class="w"> </span><span class="p">[]</span><span class="w"> </span><span class="kr">as</span><span class="w"> </span><span class="kt">string</span><span class="p">[],</span>
</span><span id="__span-26-39"><a id="__codelineno-26-39" name="__codelineno-26-39" href="#__codelineno-26-39"></a><span class="w"> </span><span class="p">};</span>
</span><span id="__span-26-40"><a id="__codelineno-26-40" name="__codelineno-26-40" href="#__codelineno-26-40"></a>
</span><span id="__span-26-41"><a id="__codelineno-26-41" name="__codelineno-26-41" href="#__codelineno-26-41"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kd">const</span><span class="w"> </span><span class="nx">filename</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="nx">videoFiles</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-26-42"><a id="__codelineno-26-42" name="__codelineno-26-42" href="#__codelineno-26-42"></a><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-26-43"><a id="__codelineno-26-43" name="__codelineno-26-43" href="#__codelineno-26-43"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">relativePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">directoryType</span><span class="p">,</span><span class="w"> </span><span class="nx">filename</span><span class="p">);</span>
</span><span id="__span-26-44"><a id="__codelineno-26-44" name="__codelineno-26-44" href="#__codelineno-26-44"></a>
</span><span id="__span-26-45"><a id="__codelineno-26-45" name="__codelineno-26-45" href="#__codelineno-26-45"></a><span class="w"> </span><span class="c1">// Check if already exists</span>
</span><span id="__span-26-46"><a id="__codelineno-26-46" name="__codelineno-26-46" href="#__codelineno-26-46"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">skipExisting</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-26-47"><a id="__codelineno-26-47" name="__codelineno-26-47" href="#__codelineno-26-47"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">existing</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">db</span>
</span><span id="__span-26-48"><a id="__codelineno-26-48" name="__codelineno-26-48" href="#__codelineno-26-48"></a><span class="w"> </span><span class="p">.</span><span class="nx">select</span><span class="p">()</span>
</span><span id="__span-26-49"><a id="__codelineno-26-49" name="__codelineno-26-49" href="#__codelineno-26-49"></a><span class="w"> </span><span class="p">.</span><span class="kr">from</span><span class="p">(</span><span class="nx">videos</span><span class="p">)</span>
</span><span id="__span-26-50"><a id="__codelineno-26-50" name="__codelineno-26-50" href="#__codelineno-26-50"></a><span class="w"> </span><span class="p">.</span><span class="nx">where</span><span class="p">(</span><span class="nx">eq</span><span class="p">(</span><span class="nx">videos</span><span class="p">.</span><span class="nx">path</span><span class="p">,</span><span class="w"> </span><span class="nx">relativePath</span><span class="p">))</span>
</span><span id="__span-26-51"><a id="__codelineno-26-51" name="__codelineno-26-51" href="#__codelineno-26-51"></a><span class="w"> </span><span class="p">.</span><span class="nx">limit</span><span class="p">(</span><span class="mf">1</span><span class="p">);</span>
</span><span id="__span-26-52"><a id="__codelineno-26-52" name="__codelineno-26-52" href="#__codelineno-26-52"></a>
</span><span id="__span-26-53"><a id="__codelineno-26-53" name="__codelineno-26-53" href="#__codelineno-26-53"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">existing</span><span class="p">.</span><span class="nx">length</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-26-54"><a id="__codelineno-26-54" name="__codelineno-26-54" href="#__codelineno-26-54"></a><span class="w"> </span><span class="nx">results</span><span class="p">.</span><span class="nx">skipped</span><span class="o">++</span><span class="p">;</span>
</span><span id="__span-26-55"><a id="__codelineno-26-55" name="__codelineno-26-55" href="#__codelineno-26-55"></a><span class="w"> </span><span class="k">continue</span><span class="p">;</span>
</span><span id="__span-26-56"><a id="__codelineno-26-56" name="__codelineno-26-56" href="#__codelineno-26-56"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-26-57"><a id="__codelineno-26-57" name="__codelineno-26-57" href="#__codelineno-26-57"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-26-58"><a id="__codelineno-26-58" name="__codelineno-26-58" href="#__codelineno-26-58"></a>
</span><span id="__span-26-59"><a id="__codelineno-26-59" name="__codelineno-26-59" href="#__codelineno-26-59"></a><span class="w"> </span><span class="c1">// Extract metadata</span>
</span><span id="__span-26-60"><a id="__codelineno-26-60" name="__codelineno-26-60" href="#__codelineno-26-60"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">fullPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">dirPath</span><span class="p">,</span><span class="w"> </span><span class="nx">filename</span><span class="p">);</span>
</span><span id="__span-26-61"><a id="__codelineno-26-61" name="__codelineno-26-61" href="#__codelineno-26-61"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">metadata</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">ffprobeService</span><span class="p">.</span><span class="nx">extract</span><span class="p">(</span><span class="nx">fullPath</span><span class="p">);</span>
</span><span id="__span-26-62"><a id="__codelineno-26-62" name="__codelineno-26-62" href="#__codelineno-26-62"></a>
</span><span id="__span-26-63"><a id="__codelineno-26-63" name="__codelineno-26-63" href="#__codelineno-26-63"></a><span class="w"> </span><span class="c1">// Create record</span>
</span><span id="__span-26-64"><a id="__codelineno-26-64" name="__codelineno-26-64" href="#__codelineno-26-64"></a><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">db</span><span class="p">.</span><span class="nx">insert</span><span class="p">(</span><span class="nx">videos</span><span class="p">).</span><span class="nx">values</span><span class="p">({</span>
</span><span id="__span-26-65"><a id="__codelineno-26-65" name="__codelineno-26-65" href="#__codelineno-26-65"></a><span class="w"> </span><span class="nx">path</span><span class="o">:</span><span class="w"> </span><span class="kt">relativePath</span><span class="p">,</span>
</span><span id="__span-26-66"><a id="__codelineno-26-66" name="__codelineno-26-66" href="#__codelineno-26-66"></a><span class="w"> </span><span class="nx">filename</span><span class="o">:</span><span class="w"> </span><span class="kt">path.basename</span><span class="p">(</span><span class="nx">filename</span><span class="p">),</span>
</span><span id="__span-26-67"><a id="__codelineno-26-67" name="__codelineno-26-67" href="#__codelineno-26-67"></a><span class="w"> </span><span class="nx">directoryType</span><span class="p">,</span>
</span><span id="__span-26-68"><a id="__codelineno-26-68" name="__codelineno-26-68" href="#__codelineno-26-68"></a><span class="w"> </span><span class="nx">durationSeconds</span><span class="o">:</span><span class="w"> </span><span class="kt">metadata.duration</span><span class="p">,</span>
</span><span id="__span-26-69"><a id="__codelineno-26-69" name="__codelineno-26-69" href="#__codelineno-26-69"></a><span class="w"> </span><span class="nx">width</span><span class="o">:</span><span class="w"> </span><span class="kt">metadata.width</span><span class="p">,</span>
</span><span id="__span-26-70"><a id="__codelineno-26-70" name="__codelineno-26-70" href="#__codelineno-26-70"></a><span class="w"> </span><span class="nx">height</span><span class="o">:</span><span class="w"> </span><span class="kt">metadata.height</span><span class="p">,</span>
</span><span id="__span-26-71"><a id="__codelineno-26-71" name="__codelineno-26-71" href="#__codelineno-26-71"></a><span class="w"> </span><span class="nx">orientation</span><span class="o">:</span><span class="w"> </span><span class="kt">metadata.orientation</span><span class="p">,</span>
</span><span id="__span-26-72"><a id="__codelineno-26-72" name="__codelineno-26-72" href="#__codelineno-26-72"></a><span class="w"> </span><span class="nx">quality</span><span class="o">:</span><span class="w"> </span><span class="kt">metadata.quality</span><span class="p">,</span>
</span><span id="__span-26-73"><a id="__codelineno-26-73" name="__codelineno-26-73" href="#__codelineno-26-73"></a><span class="w"> </span><span class="nx">hasAudio</span><span class="o">:</span><span class="w"> </span><span class="kt">metadata.hasAudio</span><span class="p">,</span>
</span><span id="__span-26-74"><a id="__codelineno-26-74" name="__codelineno-26-74" href="#__codelineno-26-74"></a><span class="w"> </span><span class="nx">fileSize</span><span class="o">:</span><span class="w"> </span><span class="kt">metadata.fileSize</span><span class="p">,</span>
</span><span id="__span-26-75"><a id="__codelineno-26-75" name="__codelineno-26-75" href="#__codelineno-26-75"></a><span class="w"> </span><span class="nx">isValid</span><span class="o">:</span><span class="w"> </span><span class="kt">true</span><span class="p">,</span>
</span><span id="__span-26-76"><a id="__codelineno-26-76" name="__codelineno-26-76" href="#__codelineno-26-76"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-26-77"><a id="__codelineno-26-77" name="__codelineno-26-77" href="#__codelineno-26-77"></a>
</span><span id="__span-26-78"><a id="__codelineno-26-78" name="__codelineno-26-78" href="#__codelineno-26-78"></a><span class="w"> </span><span class="nx">results</span><span class="p">.</span><span class="nx">created</span><span class="o">++</span><span class="p">;</span>
</span><span id="__span-26-79"><a id="__codelineno-26-79" name="__codelineno-26-79" href="#__codelineno-26-79"></a><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="nx">error</span><span class="o">:</span><span class="w"> </span><span class="kt">any</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-26-80"><a id="__codelineno-26-80" name="__codelineno-26-80" href="#__codelineno-26-80"></a><span class="w"> </span><span class="nx">results</span><span class="p">.</span><span class="nx">failed</span><span class="o">++</span><span class="p">;</span>
</span><span id="__span-26-81"><a id="__codelineno-26-81" name="__codelineno-26-81" href="#__codelineno-26-81"></a><span class="w"> </span><span class="nx">results</span><span class="p">.</span><span class="nx">errors</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="sb">`</span><span class="si">${</span><span class="nx">filename</span><span class="si">}</span><span class="sb">: </span><span class="si">${</span><span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span><span id="__span-26-82"><a id="__codelineno-26-82" name="__codelineno-26-82" href="#__codelineno-26-82"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-26-83"><a id="__codelineno-26-83" name="__codelineno-26-83" href="#__codelineno-26-83"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-26-84"><a id="__codelineno-26-84" name="__codelineno-26-84" href="#__codelineno-26-84"></a>
</span><span id="__span-26-85"><a id="__codelineno-26-85" name="__codelineno-26-85" href="#__codelineno-26-85"></a><span class="w"> </span><span class="nx">reply</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">results</span><span class="p">);</span>
</span><span id="__span-26-86"><a id="__codelineno-26-86" name="__codelineno-26-86" href="#__codelineno-26-86"></a><span class="p">});</span>
</span></code></pre></div>
<hr />
<h3 id="validate-video-metadata">Validate Video Metadata<a class="headerlink" href="#validate-video-metadata" title="Permanent link">&para;</a></h3>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-27-1"><a id="__codelineno-27-1" name="__codelineno-27-1" href="#__codelineno-27-1"></a><span class="c1">// api/src/modules/media/routes/videos.routes.ts</span>
</span><span id="__span-27-2"><a id="__codelineno-27-2" name="__codelineno-27-2" href="#__codelineno-27-2"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">eq</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;drizzle-orm&#39;</span><span class="p">;</span>
</span><span id="__span-27-3"><a id="__codelineno-27-3" name="__codelineno-27-3" href="#__codelineno-27-3"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">videos</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;@/modules/media/db/schema&#39;</span><span class="p">;</span>
</span><span id="__span-27-4"><a id="__codelineno-27-4" name="__codelineno-27-4" href="#__codelineno-27-4"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">ffprobeService</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;@/modules/media/services/ffprobe.service&#39;</span><span class="p">;</span>
</span><span id="__span-27-5"><a id="__codelineno-27-5" name="__codelineno-27-5" href="#__codelineno-27-5"></a>
</span><span id="__span-27-6"><a id="__codelineno-27-6" name="__codelineno-27-6" href="#__codelineno-27-6"></a><span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s1">&#39;/api/media/videos/:id/validate&#39;</span><span class="p">,</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="p">(</span><span class="nx">req</span><span class="p">,</span><span class="w"> </span><span class="nx">reply</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-27-7"><a id="__codelineno-27-7" name="__codelineno-27-7" href="#__codelineno-27-7"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">id</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">params</span><span class="w"> </span><span class="kr">as</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">id</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">};</span>
</span><span id="__span-27-8"><a id="__codelineno-27-8" name="__codelineno-27-8" href="#__codelineno-27-8"></a>
</span><span id="__span-27-9"><a id="__codelineno-27-9" name="__codelineno-27-9" href="#__codelineno-27-9"></a><span class="w"> </span><span class="c1">// Fetch video record</span>
</span><span id="__span-27-10"><a id="__codelineno-27-10" name="__codelineno-27-10" href="#__codelineno-27-10"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">[</span><span class="nx">video</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">db</span>
</span><span id="__span-27-11"><a id="__codelineno-27-11" name="__codelineno-27-11" href="#__codelineno-27-11"></a><span class="w"> </span><span class="p">.</span><span class="nx">select</span><span class="p">()</span>
</span><span id="__span-27-12"><a id="__codelineno-27-12" name="__codelineno-27-12" href="#__codelineno-27-12"></a><span class="w"> </span><span class="p">.</span><span class="kr">from</span><span class="p">(</span><span class="nx">videos</span><span class="p">)</span>
</span><span id="__span-27-13"><a id="__codelineno-27-13" name="__codelineno-27-13" href="#__codelineno-27-13"></a><span class="w"> </span><span class="p">.</span><span class="nx">where</span><span class="p">(</span><span class="nx">eq</span><span class="p">(</span><span class="nx">videos</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="p">))</span>
</span><span id="__span-27-14"><a id="__codelineno-27-14" name="__codelineno-27-14" href="#__codelineno-27-14"></a><span class="w"> </span><span class="p">.</span><span class="nx">limit</span><span class="p">(</span><span class="mf">1</span><span class="p">);</span>
</span><span id="__span-27-15"><a id="__codelineno-27-15" name="__codelineno-27-15" href="#__codelineno-27-15"></a>
</span><span id="__span-27-16"><a id="__codelineno-27-16" name="__codelineno-27-16" href="#__codelineno-27-16"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nx">video</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-27-17"><a id="__codelineno-27-17" name="__codelineno-27-17" href="#__codelineno-27-17"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">reply</span><span class="p">.</span><span class="nx">code</span><span class="p">(</span><span class="mf">404</span><span class="p">).</span><span class="nx">send</span><span class="p">({</span><span class="w"> </span><span class="nx">error</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;Video not found&#39;</span><span class="w"> </span><span class="p">});</span>
</span><span id="__span-27-18"><a id="__codelineno-27-18" name="__codelineno-27-18" href="#__codelineno-27-18"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-27-19"><a id="__codelineno-27-19" name="__codelineno-27-19" href="#__codelineno-27-19"></a>
</span><span id="__span-27-20"><a id="__codelineno-27-20" name="__codelineno-27-20" href="#__codelineno-27-20"></a><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-27-21"><a id="__codelineno-27-21" name="__codelineno-27-21" href="#__codelineno-27-21"></a><span class="w"> </span><span class="c1">// Build full file path</span>
</span><span id="__span-27-22"><a id="__codelineno-27-22" name="__codelineno-27-22" href="#__codelineno-27-22"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">fullPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">MEDIA_LIBRARY_PATH</span><span class="o">!</span><span class="p">,</span><span class="w"> </span><span class="nx">video</span><span class="p">.</span><span class="nx">path</span><span class="p">);</span>
</span><span id="__span-27-23"><a id="__codelineno-27-23" name="__codelineno-27-23" href="#__codelineno-27-23"></a>
</span><span id="__span-27-24"><a id="__codelineno-27-24" name="__codelineno-27-24" href="#__codelineno-27-24"></a><span class="w"> </span><span class="c1">// Extract fresh metadata</span>
</span><span id="__span-27-25"><a id="__codelineno-27-25" name="__codelineno-27-25" href="#__codelineno-27-25"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">metadata</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">ffprobeService</span><span class="p">.</span><span class="nx">extract</span><span class="p">(</span><span class="nx">fullPath</span><span class="p">);</span>
</span><span id="__span-27-26"><a id="__codelineno-27-26" name="__codelineno-27-26" href="#__codelineno-27-26"></a>
</span><span id="__span-27-27"><a id="__codelineno-27-27" name="__codelineno-27-27" href="#__codelineno-27-27"></a><span class="w"> </span><span class="c1">// Update database</span>
</span><span id="__span-27-28"><a id="__codelineno-27-28" name="__codelineno-27-28" href="#__codelineno-27-28"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">[</span><span class="nx">updated</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">db</span>
</span><span id="__span-27-29"><a id="__codelineno-27-29" name="__codelineno-27-29" href="#__codelineno-27-29"></a><span class="w"> </span><span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">videos</span><span class="p">)</span>
</span><span id="__span-27-30"><a id="__codelineno-27-30" name="__codelineno-27-30" href="#__codelineno-27-30"></a><span class="w"> </span><span class="p">.</span><span class="nx">set</span><span class="p">({</span>
</span><span id="__span-27-31"><a id="__codelineno-27-31" name="__codelineno-27-31" href="#__codelineno-27-31"></a><span class="w"> </span><span class="nx">durationSeconds</span><span class="o">:</span><span class="w"> </span><span class="kt">metadata.duration</span><span class="p">,</span>
</span><span id="__span-27-32"><a id="__codelineno-27-32" name="__codelineno-27-32" href="#__codelineno-27-32"></a><span class="w"> </span><span class="nx">width</span><span class="o">:</span><span class="w"> </span><span class="kt">metadata.width</span><span class="p">,</span>
</span><span id="__span-27-33"><a id="__codelineno-27-33" name="__codelineno-27-33" href="#__codelineno-27-33"></a><span class="w"> </span><span class="nx">height</span><span class="o">:</span><span class="w"> </span><span class="kt">metadata.height</span><span class="p">,</span>
</span><span id="__span-27-34"><a id="__codelineno-27-34" name="__codelineno-27-34" href="#__codelineno-27-34"></a><span class="w"> </span><span class="nx">orientation</span><span class="o">:</span><span class="w"> </span><span class="kt">metadata.orientation</span><span class="p">,</span>
</span><span id="__span-27-35"><a id="__codelineno-27-35" name="__codelineno-27-35" href="#__codelineno-27-35"></a><span class="w"> </span><span class="nx">quality</span><span class="o">:</span><span class="w"> </span><span class="kt">metadata.quality</span><span class="p">,</span>
</span><span id="__span-27-36"><a id="__codelineno-27-36" name="__codelineno-27-36" href="#__codelineno-27-36"></a><span class="w"> </span><span class="nx">hasAudio</span><span class="o">:</span><span class="w"> </span><span class="kt">metadata.hasAudio</span><span class="p">,</span>
</span><span id="__span-27-37"><a id="__codelineno-27-37" name="__codelineno-27-37" href="#__codelineno-27-37"></a><span class="w"> </span><span class="nx">fileSize</span><span class="o">:</span><span class="w"> </span><span class="kt">metadata.fileSize</span><span class="p">,</span>
</span><span id="__span-27-38"><a id="__codelineno-27-38" name="__codelineno-27-38" href="#__codelineno-27-38"></a><span class="w"> </span><span class="nx">fileHash</span><span class="o">:</span><span class="w"> </span><span class="kt">metadata.fileHash</span><span class="p">,</span>
</span><span id="__span-27-39"><a id="__codelineno-27-39" name="__codelineno-27-39" href="#__codelineno-27-39"></a><span class="w"> </span><span class="nx">isValid</span><span class="o">:</span><span class="w"> </span><span class="kt">true</span><span class="p">,</span>
</span><span id="__span-27-40"><a id="__codelineno-27-40" name="__codelineno-27-40" href="#__codelineno-27-40"></a><span class="w"> </span><span class="nx">lastValidated</span><span class="o">:</span><span class="w"> </span><span class="kt">new</span><span class="w"> </span><span class="nb">Date</span><span class="p">(),</span>
</span><span id="__span-27-41"><a id="__codelineno-27-41" name="__codelineno-27-41" href="#__codelineno-27-41"></a><span class="w"> </span><span class="nx">updatedAt</span><span class="o">:</span><span class="w"> </span><span class="kt">new</span><span class="w"> </span><span class="nb">Date</span><span class="p">(),</span>
</span><span id="__span-27-42"><a id="__codelineno-27-42" name="__codelineno-27-42" href="#__codelineno-27-42"></a><span class="w"> </span><span class="p">})</span>
</span><span id="__span-27-43"><a id="__codelineno-27-43" name="__codelineno-27-43" href="#__codelineno-27-43"></a><span class="w"> </span><span class="p">.</span><span class="nx">where</span><span class="p">(</span><span class="nx">eq</span><span class="p">(</span><span class="nx">videos</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="p">))</span>
</span><span id="__span-27-44"><a id="__codelineno-27-44" name="__codelineno-27-44" href="#__codelineno-27-44"></a><span class="w"> </span><span class="p">.</span><span class="nx">returning</span><span class="p">();</span>
</span><span id="__span-27-45"><a id="__codelineno-27-45" name="__codelineno-27-45" href="#__codelineno-27-45"></a>
</span><span id="__span-27-46"><a id="__codelineno-27-46" name="__codelineno-27-46" href="#__codelineno-27-46"></a><span class="w"> </span><span class="nx">reply</span><span class="p">.</span><span class="nx">send</span><span class="p">({</span>
</span><span id="__span-27-47"><a id="__codelineno-27-47" name="__codelineno-27-47" href="#__codelineno-27-47"></a><span class="w"> </span><span class="nx">id</span><span class="o">:</span><span class="w"> </span><span class="kt">updated.id</span><span class="p">,</span>
</span><span id="__span-27-48"><a id="__codelineno-27-48" name="__codelineno-27-48" href="#__codelineno-27-48"></a><span class="w"> </span><span class="nx">isValid</span><span class="o">:</span><span class="w"> </span><span class="kt">updated.isValid</span><span class="p">,</span>
</span><span id="__span-27-49"><a id="__codelineno-27-49" name="__codelineno-27-49" href="#__codelineno-27-49"></a><span class="w"> </span><span class="nx">lastValidated</span><span class="o">:</span><span class="w"> </span><span class="kt">updated.lastValidated</span><span class="p">,</span>
</span><span id="__span-27-50"><a id="__codelineno-27-50" name="__codelineno-27-50" href="#__codelineno-27-50"></a><span class="w"> </span><span class="nx">metadata</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-27-51"><a id="__codelineno-27-51" name="__codelineno-27-51" href="#__codelineno-27-51"></a><span class="w"> </span><span class="nx">durationSeconds</span><span class="o">:</span><span class="w"> </span><span class="kt">updated.durationSeconds</span><span class="p">,</span>
</span><span id="__span-27-52"><a id="__codelineno-27-52" name="__codelineno-27-52" href="#__codelineno-27-52"></a><span class="w"> </span><span class="nx">width</span><span class="o">:</span><span class="w"> </span><span class="kt">updated.width</span><span class="p">,</span>
</span><span id="__span-27-53"><a id="__codelineno-27-53" name="__codelineno-27-53" href="#__codelineno-27-53"></a><span class="w"> </span><span class="nx">height</span><span class="o">:</span><span class="w"> </span><span class="kt">updated.height</span><span class="p">,</span>
</span><span id="__span-27-54"><a id="__codelineno-27-54" name="__codelineno-27-54" href="#__codelineno-27-54"></a><span class="w"> </span><span class="nx">quality</span><span class="o">:</span><span class="w"> </span><span class="kt">updated.quality</span><span class="p">,</span>
</span><span id="__span-27-55"><a id="__codelineno-27-55" name="__codelineno-27-55" href="#__codelineno-27-55"></a><span class="w"> </span><span class="nx">orientation</span><span class="o">:</span><span class="w"> </span><span class="kt">updated.orientation</span><span class="p">,</span>
</span><span id="__span-27-56"><a id="__codelineno-27-56" name="__codelineno-27-56" href="#__codelineno-27-56"></a><span class="w"> </span><span class="nx">hasAudio</span><span class="o">:</span><span class="w"> </span><span class="kt">updated.hasAudio</span><span class="p">,</span>
</span><span id="__span-27-57"><a id="__codelineno-27-57" name="__codelineno-27-57" href="#__codelineno-27-57"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-27-58"><a id="__codelineno-27-58" name="__codelineno-27-58" href="#__codelineno-27-58"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-27-59"><a id="__codelineno-27-59" name="__codelineno-27-59" href="#__codelineno-27-59"></a><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="nx">error</span><span class="o">:</span><span class="w"> </span><span class="kt">any</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-27-60"><a id="__codelineno-27-60" name="__codelineno-27-60" href="#__codelineno-27-60"></a><span class="w"> </span><span class="c1">// Mark as invalid if validation fails</span>
</span><span id="__span-27-61"><a id="__codelineno-27-61" name="__codelineno-27-61" href="#__codelineno-27-61"></a><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">db</span>
</span><span id="__span-27-62"><a id="__codelineno-27-62" name="__codelineno-27-62" href="#__codelineno-27-62"></a><span class="w"> </span><span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">videos</span><span class="p">)</span>
</span><span id="__span-27-63"><a id="__codelineno-27-63" name="__codelineno-27-63" href="#__codelineno-27-63"></a><span class="w"> </span><span class="p">.</span><span class="nx">set</span><span class="p">({</span>
</span><span id="__span-27-64"><a id="__codelineno-27-64" name="__codelineno-27-64" href="#__codelineno-27-64"></a><span class="w"> </span><span class="nx">isValid</span><span class="o">:</span><span class="w"> </span><span class="kt">false</span><span class="p">,</span>
</span><span id="__span-27-65"><a id="__codelineno-27-65" name="__codelineno-27-65" href="#__codelineno-27-65"></a><span class="w"> </span><span class="nx">lastValidated</span><span class="o">:</span><span class="w"> </span><span class="kt">new</span><span class="w"> </span><span class="nb">Date</span><span class="p">(),</span>
</span><span id="__span-27-66"><a id="__codelineno-27-66" name="__codelineno-27-66" href="#__codelineno-27-66"></a><span class="w"> </span><span class="nx">updatedAt</span><span class="o">:</span><span class="w"> </span><span class="kt">new</span><span class="w"> </span><span class="nb">Date</span><span class="p">(),</span>
</span><span id="__span-27-67"><a id="__codelineno-27-67" name="__codelineno-27-67" href="#__codelineno-27-67"></a><span class="w"> </span><span class="p">})</span>
</span><span id="__span-27-68"><a id="__codelineno-27-68" name="__codelineno-27-68" href="#__codelineno-27-68"></a><span class="w"> </span><span class="p">.</span><span class="nx">where</span><span class="p">(</span><span class="nx">eq</span><span class="p">(</span><span class="nx">videos</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="p">));</span>
</span><span id="__span-27-69"><a id="__codelineno-27-69" name="__codelineno-27-69" href="#__codelineno-27-69"></a>
</span><span id="__span-27-70"><a id="__codelineno-27-70" name="__codelineno-27-70" href="#__codelineno-27-70"></a><span class="w"> </span><span class="nx">reply</span><span class="p">.</span><span class="nx">code</span><span class="p">(</span><span class="mf">500</span><span class="p">).</span><span class="nx">send</span><span class="p">({</span>
</span><span id="__span-27-71"><a id="__codelineno-27-71" name="__codelineno-27-71" href="#__codelineno-27-71"></a><span class="w"> </span><span class="nx">error</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;Validation failed&#39;</span><span class="p">,</span>
</span><span id="__span-27-72"><a id="__codelineno-27-72" name="__codelineno-27-72" href="#__codelineno-27-72"></a><span class="w"> </span><span class="nx">message</span><span class="o">:</span><span class="w"> </span><span class="kt">error.message</span><span class="p">,</span>
</span><span id="__span-27-73"><a id="__codelineno-27-73" name="__codelineno-27-73" href="#__codelineno-27-73"></a><span class="w"> </span><span class="nx">isValid</span><span class="o">:</span><span class="w"> </span><span class="kt">false</span><span class="p">,</span>
</span><span id="__span-27-74"><a id="__codelineno-27-74" name="__codelineno-27-74" href="#__codelineno-27-74"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-27-75"><a id="__codelineno-27-75" name="__codelineno-27-75" href="#__codelineno-27-75"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-27-76"><a id="__codelineno-27-76" name="__codelineno-27-76" href="#__codelineno-27-76"></a><span class="p">});</span>
</span></code></pre></div>
<hr />
<h3 id="frontend-library-page-table">Frontend: Library Page Table<a class="headerlink" href="#frontend-library-page-table" title="Permanent link">&para;</a></h3>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-28-1"><a id="__codelineno-28-1" name="__codelineno-28-1" href="#__codelineno-28-1"></a><span class="c1">// admin/src/pages/media/LibraryPage.tsx</span>
</span><span id="__span-28-2"><a id="__codelineno-28-2" name="__codelineno-28-2" href="#__codelineno-28-2"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">Table</span><span class="p">,</span><span class="w"> </span><span class="nx">Button</span><span class="p">,</span><span class="w"> </span><span class="nx">Select</span><span class="p">,</span><span class="w"> </span><span class="nx">Input</span><span class="p">,</span><span class="w"> </span><span class="nx">Tag</span><span class="p">,</span><span class="w"> </span><span class="nx">Space</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;antd&#39;</span><span class="p">;</span>
</span><span id="__span-28-3"><a id="__codelineno-28-3" name="__codelineno-28-3" href="#__codelineno-28-3"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">useEffect</span><span class="p">,</span><span class="w"> </span><span class="nx">useState</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span><span id="__span-28-4"><a id="__codelineno-28-4" name="__codelineno-28-4" href="#__codelineno-28-4"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">mediaApi</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;@/lib/media-api&#39;</span><span class="p">;</span>
</span><span id="__span-28-5"><a id="__codelineno-28-5" name="__codelineno-28-5" href="#__codelineno-28-5"></a>
</span><span id="__span-28-6"><a id="__codelineno-28-6" name="__codelineno-28-6" href="#__codelineno-28-6"></a><span class="k">export</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">LibraryPage</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-7"><a id="__codelineno-28-7" name="__codelineno-28-7" href="#__codelineno-28-7"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">[</span><span class="nx">videos</span><span class="p">,</span><span class="w"> </span><span class="nx">setVideos</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">useState</span><span class="p">([]);</span>
</span><span id="__span-28-8"><a id="__codelineno-28-8" name="__codelineno-28-8" href="#__codelineno-28-8"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">[</span><span class="nx">loading</span><span class="p">,</span><span class="w"> </span><span class="nx">setLoading</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">useState</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
</span><span id="__span-28-9"><a id="__codelineno-28-9" name="__codelineno-28-9" href="#__codelineno-28-9"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">[</span><span class="nx">pagination</span><span class="p">,</span><span class="w"> </span><span class="nx">setPagination</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">useState</span><span class="p">({</span><span class="w"> </span><span class="nx">page</span><span class="o">:</span><span class="w"> </span><span class="kt">1</span><span class="p">,</span><span class="w"> </span><span class="nx">limit</span><span class="o">:</span><span class="w"> </span><span class="kt">20</span><span class="p">,</span><span class="w"> </span><span class="nx">total</span><span class="o">:</span><span class="w"> </span><span class="kt">0</span><span class="w"> </span><span class="p">});</span>
</span><span id="__span-28-10"><a id="__codelineno-28-10" name="__codelineno-28-10" href="#__codelineno-28-10"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">[</span><span class="nx">filters</span><span class="p">,</span><span class="w"> </span><span class="nx">setFilters</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">useState</span><span class="p">({</span>
</span><span id="__span-28-11"><a id="__codelineno-28-11" name="__codelineno-28-11" href="#__codelineno-28-11"></a><span class="w"> </span><span class="nx">directoryType</span><span class="o">:</span><span class="w"> </span><span class="kt">undefined</span><span class="p">,</span>
</span><span id="__span-28-12"><a id="__codelineno-28-12" name="__codelineno-28-12" href="#__codelineno-28-12"></a><span class="w"> </span><span class="nx">orientation</span><span class="o">:</span><span class="w"> </span><span class="kt">undefined</span><span class="p">,</span>
</span><span id="__span-28-13"><a id="__codelineno-28-13" name="__codelineno-28-13" href="#__codelineno-28-13"></a><span class="w"> </span><span class="nx">search</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="p">,</span>
</span><span id="__span-28-14"><a id="__codelineno-28-14" name="__codelineno-28-14" href="#__codelineno-28-14"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-28-15"><a id="__codelineno-28-15" name="__codelineno-28-15" href="#__codelineno-28-15"></a>
</span><span id="__span-28-16"><a id="__codelineno-28-16" name="__codelineno-28-16" href="#__codelineno-28-16"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">fetchVideos</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-17"><a id="__codelineno-28-17" name="__codelineno-28-17" href="#__codelineno-28-17"></a><span class="w"> </span><span class="nx">setLoading</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
</span><span id="__span-28-18"><a id="__codelineno-28-18" name="__codelineno-28-18" href="#__codelineno-28-18"></a><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-19"><a id="__codelineno-28-19" name="__codelineno-28-19" href="#__codelineno-28-19"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">data</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">mediaApi</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;/api/media/videos&#39;</span><span class="p">,</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-20"><a id="__codelineno-28-20" name="__codelineno-28-20" href="#__codelineno-28-20"></a><span class="w"> </span><span class="nx">params</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-21"><a id="__codelineno-28-21" name="__codelineno-28-21" href="#__codelineno-28-21"></a><span class="w"> </span><span class="nx">page</span><span class="o">:</span><span class="w"> </span><span class="kt">pagination.page</span><span class="p">,</span>
</span><span id="__span-28-22"><a id="__codelineno-28-22" name="__codelineno-28-22" href="#__codelineno-28-22"></a><span class="w"> </span><span class="nx">limit</span><span class="o">:</span><span class="w"> </span><span class="kt">pagination.limit</span><span class="p">,</span>
</span><span id="__span-28-23"><a id="__codelineno-28-23" name="__codelineno-28-23" href="#__codelineno-28-23"></a><span class="w"> </span><span class="p">...</span><span class="nx">filters</span><span class="p">,</span>
</span><span id="__span-28-24"><a id="__codelineno-28-24" name="__codelineno-28-24" href="#__codelineno-28-24"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-28-25"><a id="__codelineno-28-25" name="__codelineno-28-25" href="#__codelineno-28-25"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-28-26"><a id="__codelineno-28-26" name="__codelineno-28-26" href="#__codelineno-28-26"></a><span class="w"> </span><span class="nx">setVideos</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span>
</span><span id="__span-28-27"><a id="__codelineno-28-27" name="__codelineno-28-27" href="#__codelineno-28-27"></a><span class="w"> </span><span class="nx">setPagination</span><span class="p">((</span><span class="nx">prev</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">({</span><span class="w"> </span><span class="p">...</span><span class="nx">prev</span><span class="p">,</span><span class="w"> </span><span class="nx">total</span><span class="o">:</span><span class="w"> </span><span class="kt">data.pagination.total</span><span class="w"> </span><span class="p">}));</span>
</span><span id="__span-28-28"><a id="__codelineno-28-28" name="__codelineno-28-28" href="#__codelineno-28-28"></a><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-29"><a id="__codelineno-28-29" name="__codelineno-28-29" href="#__codelineno-28-29"></a><span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s1">&#39;Failed to fetch videos:&#39;</span><span class="p">,</span><span class="w"> </span><span class="nx">error</span><span class="p">);</span>
</span><span id="__span-28-30"><a id="__codelineno-28-30" name="__codelineno-28-30" href="#__codelineno-28-30"></a><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">finally</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-31"><a id="__codelineno-28-31" name="__codelineno-28-31" href="#__codelineno-28-31"></a><span class="w"> </span><span class="nx">setLoading</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
</span><span id="__span-28-32"><a id="__codelineno-28-32" name="__codelineno-28-32" href="#__codelineno-28-32"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-28-33"><a id="__codelineno-28-33" name="__codelineno-28-33" href="#__codelineno-28-33"></a><span class="w"> </span><span class="p">};</span>
</span><span id="__span-28-34"><a id="__codelineno-28-34" name="__codelineno-28-34" href="#__codelineno-28-34"></a>
</span><span id="__span-28-35"><a id="__codelineno-28-35" name="__codelineno-28-35" href="#__codelineno-28-35"></a><span class="w"> </span><span class="nx">useEffect</span><span class="p">(()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-36"><a id="__codelineno-28-36" name="__codelineno-28-36" href="#__codelineno-28-36"></a><span class="w"> </span><span class="nx">fetchVideos</span><span class="p">();</span>
</span><span id="__span-28-37"><a id="__codelineno-28-37" name="__codelineno-28-37" href="#__codelineno-28-37"></a><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">[</span><span class="nx">pagination</span><span class="p">.</span><span class="nx">page</span><span class="p">,</span><span class="w"> </span><span class="nx">filters</span><span class="p">]);</span>
</span><span id="__span-28-38"><a id="__codelineno-28-38" name="__codelineno-28-38" href="#__codelineno-28-38"></a>
</span><span id="__span-28-39"><a id="__codelineno-28-39" name="__codelineno-28-39" href="#__codelineno-28-39"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">columns</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span>
</span><span id="__span-28-40"><a id="__codelineno-28-40" name="__codelineno-28-40" href="#__codelineno-28-40"></a><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-41"><a id="__codelineno-28-41" name="__codelineno-28-41" href="#__codelineno-28-41"></a><span class="w"> </span><span class="nx">title</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;Preview&#39;</span><span class="p">,</span>
</span><span id="__span-28-42"><a id="__codelineno-28-42" name="__codelineno-28-42" href="#__codelineno-28-42"></a><span class="w"> </span><span class="nx">dataIndex</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;thumbnailPath&#39;</span><span class="p">,</span>
</span><span id="__span-28-43"><a id="__codelineno-28-43" name="__codelineno-28-43" href="#__codelineno-28-43"></a><span class="w"> </span><span class="nx">width</span><span class="o">:</span><span class="w"> </span><span class="kt">100</span><span class="p">,</span>
</span><span id="__span-28-44"><a id="__codelineno-28-44" name="__codelineno-28-44" href="#__codelineno-28-44"></a><span class="w"> </span><span class="nx">render</span><span class="o">:</span><span class="w"> </span><span class="p">(</span><span class="nx">path</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">(</span>
</span><span id="__span-28-45"><a id="__codelineno-28-45" name="__codelineno-28-45" href="#__codelineno-28-45"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">img</span>
</span><span id="__span-28-46"><a id="__codelineno-28-46" name="__codelineno-28-46" href="#__codelineno-28-46"></a><span class="w"> </span><span class="nx">src</span><span class="o">=</span><span class="p">{</span><span class="nx">path</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="s1">&#39;/placeholder.jpg&#39;</span><span class="p">}</span>
</span><span id="__span-28-47"><a id="__codelineno-28-47" name="__codelineno-28-47" href="#__codelineno-28-47"></a><span class="w"> </span><span class="nx">alt</span><span class="o">=</span><span class="s2">&quot;Thumbnail&quot;</span>
</span><span id="__span-28-48"><a id="__codelineno-28-48" name="__codelineno-28-48" href="#__codelineno-28-48"></a><span class="w"> </span><span class="nx">style</span><span class="o">=</span><span class="p">{{</span><span class="w"> </span><span class="nx">width</span><span class="o">:</span><span class="w"> </span><span class="kt">80</span><span class="p">,</span><span class="w"> </span><span class="nx">height</span><span class="o">:</span><span class="w"> </span><span class="kt">60</span><span class="p">,</span><span class="w"> </span><span class="nx">objectFit</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;cover&#39;</span><span class="w"> </span><span class="p">}}</span>
</span><span id="__span-28-49"><a id="__codelineno-28-49" name="__codelineno-28-49" href="#__codelineno-28-49"></a><span class="w"> </span><span class="o">/&gt;</span>
</span><span id="__span-28-50"><a id="__codelineno-28-50" name="__codelineno-28-50" href="#__codelineno-28-50"></a><span class="w"> </span><span class="p">),</span>
</span><span id="__span-28-51"><a id="__codelineno-28-51" name="__codelineno-28-51" href="#__codelineno-28-51"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-28-52"><a id="__codelineno-28-52" name="__codelineno-28-52" href="#__codelineno-28-52"></a><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-53"><a id="__codelineno-28-53" name="__codelineno-28-53" href="#__codelineno-28-53"></a><span class="w"> </span><span class="nx">title</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;Title&#39;</span><span class="p">,</span>
</span><span id="__span-28-54"><a id="__codelineno-28-54" name="__codelineno-28-54" href="#__codelineno-28-54"></a><span class="w"> </span><span class="nx">dataIndex</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;title&#39;</span><span class="p">,</span>
</span><span id="__span-28-55"><a id="__codelineno-28-55" name="__codelineno-28-55" href="#__codelineno-28-55"></a><span class="w"> </span><span class="nx">render</span><span class="o">:</span><span class="w"> </span><span class="p">(</span><span class="nx">text</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">record</span><span class="o">:</span><span class="w"> </span><span class="kt">any</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">(</span>
</span><span id="__span-28-56"><a id="__codelineno-28-56" name="__codelineno-28-56" href="#__codelineno-28-56"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span>
</span><span id="__span-28-57"><a id="__codelineno-28-57" name="__codelineno-28-57" href="#__codelineno-28-57"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">div</span><span class="w"> </span><span class="nx">style</span><span class="o">=</span><span class="p">{{</span><span class="w"> </span><span class="nx">fontWeight</span><span class="o">:</span><span class="w"> </span><span class="kt">600</span><span class="w"> </span><span class="p">}}</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">text</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="nx">record</span><span class="p">.</span><span class="nx">filename</span><span class="p">}</span><span class="o">&lt;</span><span class="err">/div&gt;</span>
</span><span id="__span-28-58"><a id="__codelineno-28-58" name="__codelineno-28-58" href="#__codelineno-28-58"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">div</span><span class="w"> </span><span class="nx">style</span><span class="o">=</span><span class="p">{{</span><span class="w"> </span><span class="nx">fontSize</span><span class="o">:</span><span class="w"> </span><span class="kt">12</span><span class="p">,</span><span class="w"> </span><span class="nx">color</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;#888&#39;</span><span class="w"> </span><span class="p">}}</span><span class="o">&gt;</span>
</span><span id="__span-28-59"><a id="__codelineno-28-59" name="__codelineno-28-59" href="#__codelineno-28-59"></a><span class="w"> </span><span class="p">{</span><span class="nx">record</span><span class="p">.</span><span class="nx">producer</span><span class="p">}</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="p">{</span><span class="nx">record</span><span class="p">.</span><span class="nx">creator</span><span class="p">}</span>
</span><span id="__span-28-60"><a id="__codelineno-28-60" name="__codelineno-28-60" href="#__codelineno-28-60"></a><span class="w"> </span><span class="o">&lt;</span><span class="err">/div&gt;</span>
</span><span id="__span-28-61"><a id="__codelineno-28-61" name="__codelineno-28-61" href="#__codelineno-28-61"></a><span class="w"> </span><span class="o">&lt;</span><span class="err">/div&gt;</span>
</span><span id="__span-28-62"><a id="__codelineno-28-62" name="__codelineno-28-62" href="#__codelineno-28-62"></a><span class="w"> </span><span class="p">),</span>
</span><span id="__span-28-63"><a id="__codelineno-28-63" name="__codelineno-28-63" href="#__codelineno-28-63"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-28-64"><a id="__codelineno-28-64" name="__codelineno-28-64" href="#__codelineno-28-64"></a><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-65"><a id="__codelineno-28-65" name="__codelineno-28-65" href="#__codelineno-28-65"></a><span class="w"> </span><span class="nx">title</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;Duration&#39;</span><span class="p">,</span>
</span><span id="__span-28-66"><a id="__codelineno-28-66" name="__codelineno-28-66" href="#__codelineno-28-66"></a><span class="w"> </span><span class="nx">dataIndex</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;durationSeconds&#39;</span><span class="p">,</span>
</span><span id="__span-28-67"><a id="__codelineno-28-67" name="__codelineno-28-67" href="#__codelineno-28-67"></a><span class="w"> </span><span class="nx">width</span><span class="o">:</span><span class="w"> </span><span class="kt">100</span><span class="p">,</span>
</span><span id="__span-28-68"><a id="__codelineno-28-68" name="__codelineno-28-68" href="#__codelineno-28-68"></a><span class="w"> </span><span class="nx">render</span><span class="o">:</span><span class="w"> </span><span class="p">(</span><span class="nx">seconds</span><span class="o">:</span><span class="w"> </span><span class="kt">number</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-69"><a id="__codelineno-28-69" name="__codelineno-28-69" href="#__codelineno-28-69"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">mins</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nx">seconds</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mf">60</span><span class="p">);</span>
</span><span id="__span-28-70"><a id="__codelineno-28-70" name="__codelineno-28-70" href="#__codelineno-28-70"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">secs</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">seconds</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mf">60</span><span class="p">;</span>
</span><span id="__span-28-71"><a id="__codelineno-28-71" name="__codelineno-28-71" href="#__codelineno-28-71"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="sb">`</span><span class="si">${</span><span class="nx">mins</span><span class="si">}</span><span class="sb">:</span><span class="si">${</span><span class="nx">secs</span><span class="p">.</span><span class="nx">toString</span><span class="p">().</span><span class="nx">padStart</span><span class="p">(</span><span class="mf">2</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;0&#39;</span><span class="p">)</span><span class="si">}</span><span class="sb">`</span><span class="p">;</span>
</span><span id="__span-28-72"><a id="__codelineno-28-72" name="__codelineno-28-72" href="#__codelineno-28-72"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-28-73"><a id="__codelineno-28-73" name="__codelineno-28-73" href="#__codelineno-28-73"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-28-74"><a id="__codelineno-28-74" name="__codelineno-28-74" href="#__codelineno-28-74"></a><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-75"><a id="__codelineno-28-75" name="__codelineno-28-75" href="#__codelineno-28-75"></a><span class="w"> </span><span class="nx">title</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;Quality&#39;</span><span class="p">,</span>
</span><span id="__span-28-76"><a id="__codelineno-28-76" name="__codelineno-28-76" href="#__codelineno-28-76"></a><span class="w"> </span><span class="nx">dataIndex</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;quality&#39;</span><span class="p">,</span>
</span><span id="__span-28-77"><a id="__codelineno-28-77" name="__codelineno-28-77" href="#__codelineno-28-77"></a><span class="w"> </span><span class="nx">width</span><span class="o">:</span><span class="w"> </span><span class="kt">80</span><span class="p">,</span>
</span><span id="__span-28-78"><a id="__codelineno-28-78" name="__codelineno-28-78" href="#__codelineno-28-78"></a><span class="w"> </span><span class="nx">render</span><span class="o">:</span><span class="w"> </span><span class="p">(</span><span class="nx">quality</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-79"><a id="__codelineno-28-79" name="__codelineno-28-79" href="#__codelineno-28-79"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">colors</span><span class="o">:</span><span class="w"> </span><span class="kt">Record</span><span class="o">&lt;</span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="kt">string</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-80"><a id="__codelineno-28-80" name="__codelineno-28-80" href="#__codelineno-28-80"></a><span class="w"> </span><span class="nx">SD</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;default&#39;</span><span class="p">,</span>
</span><span id="__span-28-81"><a id="__codelineno-28-81" name="__codelineno-28-81" href="#__codelineno-28-81"></a><span class="w"> </span><span class="nx">HD</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;blue&#39;</span><span class="p">,</span>
</span><span id="__span-28-82"><a id="__codelineno-28-82" name="__codelineno-28-82" href="#__codelineno-28-82"></a><span class="w"> </span><span class="nx">FHD</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;green&#39;</span><span class="p">,</span>
</span><span id="__span-28-83"><a id="__codelineno-28-83" name="__codelineno-28-83" href="#__codelineno-28-83"></a><span class="w"> </span><span class="nx">UHD</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;purple&#39;</span><span class="p">,</span>
</span><span id="__span-28-84"><a id="__codelineno-28-84" name="__codelineno-28-84" href="#__codelineno-28-84"></a><span class="w"> </span><span class="p">};</span>
</span><span id="__span-28-85"><a id="__codelineno-28-85" name="__codelineno-28-85" href="#__codelineno-28-85"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="o">&lt;</span><span class="nx">Tag</span><span class="w"> </span><span class="nx">color</span><span class="o">=</span><span class="p">{</span><span class="nx">colors</span><span class="p">[</span><span class="nx">quality</span><span class="p">]}</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">quality</span><span class="p">}</span><span class="o">&lt;</span><span class="err">/Tag&gt;;</span>
</span><span id="__span-28-86"><a id="__codelineno-28-86" name="__codelineno-28-86" href="#__codelineno-28-86"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-28-87"><a id="__codelineno-28-87" name="__codelineno-28-87" href="#__codelineno-28-87"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-28-88"><a id="__codelineno-28-88" name="__codelineno-28-88" href="#__codelineno-28-88"></a><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-89"><a id="__codelineno-28-89" name="__codelineno-28-89" href="#__codelineno-28-89"></a><span class="w"> </span><span class="nx">title</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;Orientation&#39;</span><span class="p">,</span>
</span><span id="__span-28-90"><a id="__codelineno-28-90" name="__codelineno-28-90" href="#__codelineno-28-90"></a><span class="w"> </span><span class="nx">dataIndex</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;orientation&#39;</span><span class="p">,</span>
</span><span id="__span-28-91"><a id="__codelineno-28-91" name="__codelineno-28-91" href="#__codelineno-28-91"></a><span class="w"> </span><span class="nx">width</span><span class="o">:</span><span class="w"> </span><span class="kt">100</span><span class="p">,</span>
</span><span id="__span-28-92"><a id="__codelineno-28-92" name="__codelineno-28-92" href="#__codelineno-28-92"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-28-93"><a id="__codelineno-28-93" name="__codelineno-28-93" href="#__codelineno-28-93"></a><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-94"><a id="__codelineno-28-94" name="__codelineno-28-94" href="#__codelineno-28-94"></a><span class="w"> </span><span class="nx">title</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;Directory&#39;</span><span class="p">,</span>
</span><span id="__span-28-95"><a id="__codelineno-28-95" name="__codelineno-28-95" href="#__codelineno-28-95"></a><span class="w"> </span><span class="nx">dataIndex</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;directoryType&#39;</span><span class="p">,</span>
</span><span id="__span-28-96"><a id="__codelineno-28-96" name="__codelineno-28-96" href="#__codelineno-28-96"></a><span class="w"> </span><span class="nx">width</span><span class="o">:</span><span class="w"> </span><span class="kt">120</span><span class="p">,</span>
</span><span id="__span-28-97"><a id="__codelineno-28-97" name="__codelineno-28-97" href="#__codelineno-28-97"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-28-98"><a id="__codelineno-28-98" name="__codelineno-28-98" href="#__codelineno-28-98"></a><span class="w"> </span><span class="p">{</span>
</span><span id="__span-28-99"><a id="__codelineno-28-99" name="__codelineno-28-99" href="#__codelineno-28-99"></a><span class="w"> </span><span class="nx">title</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;Actions&#39;</span><span class="p">,</span>
</span><span id="__span-28-100"><a id="__codelineno-28-100" name="__codelineno-28-100" href="#__codelineno-28-100"></a><span class="w"> </span><span class="nx">width</span><span class="o">:</span><span class="w"> </span><span class="kt">150</span><span class="p">,</span>
</span><span id="__span-28-101"><a id="__codelineno-28-101" name="__codelineno-28-101" href="#__codelineno-28-101"></a><span class="w"> </span><span class="nx">render</span><span class="o">:</span><span class="w"> </span><span class="p">(</span><span class="nx">_</span><span class="o">:</span><span class="w"> </span><span class="kt">any</span><span class="p">,</span><span class="w"> </span><span class="nx">record</span><span class="o">:</span><span class="w"> </span><span class="kt">any</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">(</span>
</span><span id="__span-28-102"><a id="__codelineno-28-102" name="__codelineno-28-102" href="#__codelineno-28-102"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">Space</span><span class="o">&gt;</span>
</span><span id="__span-28-103"><a id="__codelineno-28-103" name="__codelineno-28-103" href="#__codelineno-28-103"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">Button</span><span class="w"> </span><span class="nx">size</span><span class="o">=</span><span class="s2">&quot;small&quot;</span><span class="w"> </span><span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">handleEdit</span><span class="p">(</span><span class="nx">record</span><span class="p">.</span><span class="nx">id</span><span class="p">)}</span><span class="o">&gt;</span>
</span><span id="__span-28-104"><a id="__codelineno-28-104" name="__codelineno-28-104" href="#__codelineno-28-104"></a><span class="w"> </span><span class="nx">Edit</span>
</span><span id="__span-28-105"><a id="__codelineno-28-105" name="__codelineno-28-105" href="#__codelineno-28-105"></a><span class="w"> </span><span class="o">&lt;</span><span class="err">/Button&gt;</span>
</span><span id="__span-28-106"><a id="__codelineno-28-106" name="__codelineno-28-106" href="#__codelineno-28-106"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">Button</span><span class="w"> </span><span class="nx">size</span><span class="o">=</span><span class="s2">&quot;small&quot;</span><span class="w"> </span><span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">handleValidate</span><span class="p">(</span><span class="nx">record</span><span class="p">.</span><span class="nx">id</span><span class="p">)}</span><span class="o">&gt;</span>
</span><span id="__span-28-107"><a id="__codelineno-28-107" name="__codelineno-28-107" href="#__codelineno-28-107"></a><span class="w"> </span><span class="nx">Validate</span>
</span><span id="__span-28-108"><a id="__codelineno-28-108" name="__codelineno-28-108" href="#__codelineno-28-108"></a><span class="w"> </span><span class="o">&lt;</span><span class="err">/Button&gt;</span>
</span><span id="__span-28-109"><a id="__codelineno-28-109" name="__codelineno-28-109" href="#__codelineno-28-109"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">Button</span><span class="w"> </span><span class="nx">size</span><span class="o">=</span><span class="s2">&quot;small&quot;</span><span class="w"> </span><span class="nx">danger</span><span class="w"> </span><span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">handleDelete</span><span class="p">(</span><span class="nx">record</span><span class="p">.</span><span class="nx">id</span><span class="p">)}</span><span class="o">&gt;</span>
</span><span id="__span-28-110"><a id="__codelineno-28-110" name="__codelineno-28-110" href="#__codelineno-28-110"></a><span class="w"> </span><span class="nx">Delete</span>
</span><span id="__span-28-111"><a id="__codelineno-28-111" name="__codelineno-28-111" href="#__codelineno-28-111"></a><span class="w"> </span><span class="o">&lt;</span><span class="err">/Button&gt;</span>
</span><span id="__span-28-112"><a id="__codelineno-28-112" name="__codelineno-28-112" href="#__codelineno-28-112"></a><span class="w"> </span><span class="o">&lt;</span><span class="err">/Space&gt;</span>
</span><span id="__span-28-113"><a id="__codelineno-28-113" name="__codelineno-28-113" href="#__codelineno-28-113"></a><span class="w"> </span><span class="p">),</span>
</span><span id="__span-28-114"><a id="__codelineno-28-114" name="__codelineno-28-114" href="#__codelineno-28-114"></a><span class="w"> </span><span class="p">},</span>
</span><span id="__span-28-115"><a id="__codelineno-28-115" name="__codelineno-28-115" href="#__codelineno-28-115"></a><span class="w"> </span><span class="p">];</span>
</span><span id="__span-28-116"><a id="__codelineno-28-116" name="__codelineno-28-116" href="#__codelineno-28-116"></a>
</span><span id="__span-28-117"><a id="__codelineno-28-117" name="__codelineno-28-117" href="#__codelineno-28-117"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">(</span>
</span><span id="__span-28-118"><a id="__codelineno-28-118" name="__codelineno-28-118" href="#__codelineno-28-118"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span>
</span><span id="__span-28-119"><a id="__codelineno-28-119" name="__codelineno-28-119" href="#__codelineno-28-119"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">Space</span><span class="w"> </span><span class="nx">style</span><span class="o">=</span><span class="p">{{</span><span class="w"> </span><span class="nx">marginBottom</span><span class="o">:</span><span class="w"> </span><span class="kt">16</span><span class="w"> </span><span class="p">}}</span><span class="o">&gt;</span>
</span><span id="__span-28-120"><a id="__codelineno-28-120" name="__codelineno-28-120" href="#__codelineno-28-120"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">Select</span>
</span><span id="__span-28-121"><a id="__codelineno-28-121" name="__codelineno-28-121" href="#__codelineno-28-121"></a><span class="w"> </span><span class="nx">placeholder</span><span class="o">=</span><span class="s2">&quot;Directory Type&quot;</span>
</span><span id="__span-28-122"><a id="__codelineno-28-122" name="__codelineno-28-122" href="#__codelineno-28-122"></a><span class="w"> </span><span class="nx">style</span><span class="o">=</span><span class="p">{{</span><span class="w"> </span><span class="nx">width</span><span class="o">:</span><span class="w"> </span><span class="kt">200</span><span class="w"> </span><span class="p">}}</span>
</span><span id="__span-28-123"><a id="__codelineno-28-123" name="__codelineno-28-123" href="#__codelineno-28-123"></a><span class="w"> </span><span class="nx">onChange</span><span class="o">=</span><span class="p">{(</span><span class="nx">value</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">setFilters</span><span class="p">({</span><span class="w"> </span><span class="p">...</span><span class="nx">filters</span><span class="p">,</span><span class="w"> </span><span class="nx">directoryType</span><span class="o">:</span><span class="w"> </span><span class="kt">value</span><span class="w"> </span><span class="p">})}</span>
</span><span id="__span-28-124"><a id="__codelineno-28-124" name="__codelineno-28-124" href="#__codelineno-28-124"></a><span class="w"> </span><span class="nx">allowClear</span>
</span><span id="__span-28-125"><a id="__codelineno-28-125" name="__codelineno-28-125" href="#__codelineno-28-125"></a><span class="w"> </span><span class="o">&gt;</span>
</span><span id="__span-28-126"><a id="__codelineno-28-126" name="__codelineno-28-126" href="#__codelineno-28-126"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">Select</span><span class="p">.</span><span class="nx">Option</span><span class="w"> </span><span class="nx">value</span><span class="o">=</span><span class="s2">&quot;videos&quot;</span><span class="o">&gt;</span><span class="nx">Videos</span><span class="o">&lt;</span><span class="err">/Select.Option&gt;</span>
</span><span id="__span-28-127"><a id="__codelineno-28-127" name="__codelineno-28-127" href="#__codelineno-28-127"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">Select</span><span class="p">.</span><span class="nx">Option</span><span class="w"> </span><span class="nx">value</span><span class="o">=</span><span class="s2">&quot;studios&quot;</span><span class="o">&gt;</span><span class="nx">Studios</span><span class="o">&lt;</span><span class="err">/Select.Option&gt;</span>
</span><span id="__span-28-128"><a id="__codelineno-28-128" name="__codelineno-28-128" href="#__codelineno-28-128"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">Select</span><span class="p">.</span><span class="nx">Option</span><span class="w"> </span><span class="nx">value</span><span class="o">=</span><span class="s2">&quot;gifs&quot;</span><span class="o">&gt;</span><span class="nx">GIFs</span><span class="o">&lt;</span><span class="err">/Select.Option&gt;</span>
</span><span id="__span-28-129"><a id="__codelineno-28-129" name="__codelineno-28-129" href="#__codelineno-28-129"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">Select</span><span class="p">.</span><span class="nx">Option</span><span class="w"> </span><span class="nx">value</span><span class="o">=</span><span class="s2">&quot;curated&quot;</span><span class="o">&gt;</span><span class="nx">Curated</span><span class="o">&lt;</span><span class="err">/Select.Option&gt;</span>
</span><span id="__span-28-130"><a id="__codelineno-28-130" name="__codelineno-28-130" href="#__codelineno-28-130"></a><span class="w"> </span><span class="o">&lt;</span><span class="err">/Select&gt;</span>
</span><span id="__span-28-131"><a id="__codelineno-28-131" name="__codelineno-28-131" href="#__codelineno-28-131"></a>
</span><span id="__span-28-132"><a id="__codelineno-28-132" name="__codelineno-28-132" href="#__codelineno-28-132"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">Input</span><span class="p">.</span><span class="nx">Search</span>
</span><span id="__span-28-133"><a id="__codelineno-28-133" name="__codelineno-28-133" href="#__codelineno-28-133"></a><span class="w"> </span><span class="nx">placeholder</span><span class="o">=</span><span class="s2">&quot;Search title, producer, creator&quot;</span>
</span><span id="__span-28-134"><a id="__codelineno-28-134" name="__codelineno-28-134" href="#__codelineno-28-134"></a><span class="w"> </span><span class="nx">style</span><span class="o">=</span><span class="p">{{</span><span class="w"> </span><span class="nx">width</span><span class="o">:</span><span class="w"> </span><span class="kt">300</span><span class="w"> </span><span class="p">}}</span>
</span><span id="__span-28-135"><a id="__codelineno-28-135" name="__codelineno-28-135" href="#__codelineno-28-135"></a><span class="w"> </span><span class="nx">onSearch</span><span class="o">=</span><span class="p">{(</span><span class="nx">value</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">setFilters</span><span class="p">({</span><span class="w"> </span><span class="p">...</span><span class="nx">filters</span><span class="p">,</span><span class="w"> </span><span class="nx">search</span><span class="o">:</span><span class="w"> </span><span class="kt">value</span><span class="w"> </span><span class="p">})}</span>
</span><span id="__span-28-136"><a id="__codelineno-28-136" name="__codelineno-28-136" href="#__codelineno-28-136"></a><span class="w"> </span><span class="nx">allowClear</span>
</span><span id="__span-28-137"><a id="__codelineno-28-137" name="__codelineno-28-137" href="#__codelineno-28-137"></a><span class="w"> </span><span class="o">/&gt;</span>
</span><span id="__span-28-138"><a id="__codelineno-28-138" name="__codelineno-28-138" href="#__codelineno-28-138"></a>
</span><span id="__span-28-139"><a id="__codelineno-28-139" name="__codelineno-28-139" href="#__codelineno-28-139"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">Button</span><span class="w"> </span><span class="kr">type</span><span class="o">=</span><span class="s2">&quot;primary&quot;</span><span class="w"> </span><span class="nx">onClick</span><span class="o">=</span><span class="p">{</span><span class="nx">handleScanDirectory</span><span class="p">}</span><span class="o">&gt;</span>
</span><span id="__span-28-140"><a id="__codelineno-28-140" name="__codelineno-28-140" href="#__codelineno-28-140"></a><span class="w"> </span><span class="nx">Scan</span><span class="w"> </span><span class="nx">Directory</span>
</span><span id="__span-28-141"><a id="__codelineno-28-141" name="__codelineno-28-141" href="#__codelineno-28-141"></a><span class="w"> </span><span class="o">&lt;</span><span class="err">/Button&gt;</span>
</span><span id="__span-28-142"><a id="__codelineno-28-142" name="__codelineno-28-142" href="#__codelineno-28-142"></a><span class="w"> </span><span class="o">&lt;</span><span class="err">/Space&gt;</span>
</span><span id="__span-28-143"><a id="__codelineno-28-143" name="__codelineno-28-143" href="#__codelineno-28-143"></a>
</span><span id="__span-28-144"><a id="__codelineno-28-144" name="__codelineno-28-144" href="#__codelineno-28-144"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">Table</span>
</span><span id="__span-28-145"><a id="__codelineno-28-145" name="__codelineno-28-145" href="#__codelineno-28-145"></a><span class="w"> </span><span class="nx">columns</span><span class="o">=</span><span class="p">{</span><span class="nx">columns</span><span class="p">}</span>
</span><span id="__span-28-146"><a id="__codelineno-28-146" name="__codelineno-28-146" href="#__codelineno-28-146"></a><span class="w"> </span><span class="nx">dataSource</span><span class="o">=</span><span class="p">{</span><span class="nx">videos</span><span class="p">}</span>
</span><span id="__span-28-147"><a id="__codelineno-28-147" name="__codelineno-28-147" href="#__codelineno-28-147"></a><span class="w"> </span><span class="nx">loading</span><span class="o">=</span><span class="p">{</span><span class="nx">loading</span><span class="p">}</span>
</span><span id="__span-28-148"><a id="__codelineno-28-148" name="__codelineno-28-148" href="#__codelineno-28-148"></a><span class="w"> </span><span class="nx">rowKey</span><span class="o">=</span><span class="s2">&quot;id&quot;</span>
</span><span id="__span-28-149"><a id="__codelineno-28-149" name="__codelineno-28-149" href="#__codelineno-28-149"></a><span class="w"> </span><span class="nx">pagination</span><span class="o">=</span><span class="p">{{</span>
</span><span id="__span-28-150"><a id="__codelineno-28-150" name="__codelineno-28-150" href="#__codelineno-28-150"></a><span class="w"> </span><span class="nx">current</span><span class="o">:</span><span class="w"> </span><span class="kt">pagination.page</span><span class="p">,</span>
</span><span id="__span-28-151"><a id="__codelineno-28-151" name="__codelineno-28-151" href="#__codelineno-28-151"></a><span class="w"> </span><span class="nx">pageSize</span><span class="o">:</span><span class="w"> </span><span class="kt">pagination.limit</span><span class="p">,</span>
</span><span id="__span-28-152"><a id="__codelineno-28-152" name="__codelineno-28-152" href="#__codelineno-28-152"></a><span class="w"> </span><span class="nx">total</span><span class="o">:</span><span class="w"> </span><span class="kt">pagination.total</span><span class="p">,</span>
</span><span id="__span-28-153"><a id="__codelineno-28-153" name="__codelineno-28-153" href="#__codelineno-28-153"></a><span class="w"> </span><span class="nx">onChange</span><span class="o">:</span><span class="w"> </span><span class="p">(</span><span class="nx">page</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">setPagination</span><span class="p">({</span><span class="w"> </span><span class="p">...</span><span class="nx">pagination</span><span class="p">,</span><span class="w"> </span><span class="nx">page</span><span class="w"> </span><span class="p">}),</span>
</span><span id="__span-28-154"><a id="__codelineno-28-154" name="__codelineno-28-154" href="#__codelineno-28-154"></a><span class="w"> </span><span class="p">}}</span>
</span><span id="__span-28-155"><a id="__codelineno-28-155" name="__codelineno-28-155" href="#__codelineno-28-155"></a><span class="w"> </span><span class="o">/&gt;</span>
</span><span id="__span-28-156"><a id="__codelineno-28-156" name="__codelineno-28-156" href="#__codelineno-28-156"></a><span class="w"> </span><span class="o">&lt;</span><span class="err">/div&gt;</span>
</span><span id="__span-28-157"><a id="__codelineno-28-157" name="__codelineno-28-157" href="#__codelineno-28-157"></a><span class="w"> </span><span class="p">);</span>
</span><span id="__span-28-158"><a id="__codelineno-28-158" name="__codelineno-28-158" href="#__codelineno-28-158"></a><span class="p">}</span>
</span></code></pre></div>
<hr />
<h2 id="troubleshooting">Troubleshooting<a class="headerlink" href="#troubleshooting" title="Permanent link">&para;</a></h2>
<h3 id="problem-media-api-not-accessible">Problem: Media API Not Accessible<a class="headerlink" href="#problem-media-api-not-accessible" title="Permanent link">&para;</a></h3>
<p><strong>Symptoms:</strong></p>
<ul>
<li>Admin GUI shows "Cannot connect to media API"</li>
<li>Browser console shows CORS errors or network failures</li>
<li>Public gallery doesn't load</li>
</ul>
<p><strong>Solutions:</strong></p>
<ol>
<li><strong>Check Fastify server running:</strong></li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-29-1"><a id="__codelineno-29-1" name="__codelineno-29-1" href="#__codelineno-29-1"></a>docker<span class="w"> </span>compose<span class="w"> </span>ps<span class="w"> </span>media-api
</span><span id="__span-29-2"><a id="__codelineno-29-2" name="__codelineno-29-2" href="#__codelineno-29-2"></a><span class="c1"># Should show &quot;Up&quot; status</span>
</span><span id="__span-29-3"><a id="__codelineno-29-3" name="__codelineno-29-3" href="#__codelineno-29-3"></a>
</span><span id="__span-29-4"><a id="__codelineno-29-4" name="__codelineno-29-4" href="#__codelineno-29-4"></a>docker<span class="w"> </span>compose<span class="w"> </span>logs<span class="w"> </span>media-api
</span><span id="__span-29-5"><a id="__codelineno-29-5" name="__codelineno-29-5" href="#__codelineno-29-5"></a><span class="c1"># Look for &quot;Fastify server listening on port 4100&quot;</span>
</span></code></pre></div>
<ol>
<li><strong>Verify port 4100 not in use:</strong></li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-30-1"><a id="__codelineno-30-1" name="__codelineno-30-1" href="#__codelineno-30-1"></a>lsof<span class="w"> </span>-i<span class="w"> </span>:4100
</span><span id="__span-30-2"><a id="__codelineno-30-2" name="__codelineno-30-2" href="#__codelineno-30-2"></a><span class="c1"># Should show only media-api container</span>
</span><span id="__span-30-3"><a id="__codelineno-30-3" name="__codelineno-30-3" href="#__codelineno-30-3"></a>
</span><span id="__span-30-4"><a id="__codelineno-30-4" name="__codelineno-30-4" href="#__codelineno-30-4"></a><span class="c1"># If another process using port, stop it or change MEDIA_API_PORT in .env</span>
</span></code></pre></div>
<ol>
<li><strong>Check nginx proxy configuration:</strong></li>
</ol>
<div class="language-nginx highlight"><pre><span></span><code><span id="__span-31-1"><a id="__codelineno-31-1" name="__codelineno-31-1" href="#__codelineno-31-1"></a><span class="c1"># nginx/conf.d/api.conf</span>
</span><span id="__span-31-2"><a id="__codelineno-31-2" name="__codelineno-31-2" href="#__codelineno-31-2"></a><span class="c1"># Media API block must come BEFORE general API block</span>
</span><span id="__span-31-3"><a id="__codelineno-31-3" name="__codelineno-31-3" href="#__codelineno-31-3"></a>
</span><span id="__span-31-4"><a id="__codelineno-31-4" name="__codelineno-31-4" href="#__codelineno-31-4"></a><span class="k">server</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-31-5"><a id="__codelineno-31-5" name="__codelineno-31-5" href="#__codelineno-31-5"></a><span class="w"> </span><span class="kn">listen</span><span class="w"> </span><span class="mi">80</span><span class="p">;</span>
</span><span id="__span-31-6"><a id="__codelineno-31-6" name="__codelineno-31-6" href="#__codelineno-31-6"></a><span class="w"> </span><span class="kn">server_name</span><span class="w"> </span><span class="s">media.cmlite.org</span><span class="p">;</span>
</span><span id="__span-31-7"><a id="__codelineno-31-7" name="__codelineno-31-7" href="#__codelineno-31-7"></a>
</span><span id="__span-31-8"><a id="__codelineno-31-8" name="__codelineno-31-8" href="#__codelineno-31-8"></a><span class="w"> </span><span class="kn">location</span><span class="w"> </span><span class="s">/</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-31-9"><a id="__codelineno-31-9" name="__codelineno-31-9" href="#__codelineno-31-9"></a><span class="w"> </span><span class="kn">proxy_pass</span><span class="w"> </span><span class="s">http://localhost:4100</span><span class="p">;</span>
</span><span id="__span-31-10"><a id="__codelineno-31-10" name="__codelineno-31-10" href="#__codelineno-31-10"></a><span class="w"> </span><span class="kn">proxy_http_version</span><span class="w"> </span><span class="mi">1</span><span class="s">.1</span><span class="p">;</span>
</span><span id="__span-31-11"><a id="__codelineno-31-11" name="__codelineno-31-11" href="#__codelineno-31-11"></a><span class="w"> </span><span class="kn">proxy_set_header</span><span class="w"> </span><span class="s">Upgrade</span><span class="w"> </span><span class="nv">$http_upgrade</span><span class="p">;</span>
</span><span id="__span-31-12"><a id="__codelineno-31-12" name="__codelineno-31-12" href="#__codelineno-31-12"></a><span class="w"> </span><span class="kn">proxy_set_header</span><span class="w"> </span><span class="s">Connection</span><span class="w"> </span><span class="s">&#39;upgrade&#39;</span><span class="p">;</span>
</span><span id="__span-31-13"><a id="__codelineno-31-13" name="__codelineno-31-13" href="#__codelineno-31-13"></a><span class="w"> </span><span class="kn">proxy_set_header</span><span class="w"> </span><span class="s">Host</span><span class="w"> </span><span class="nv">$host</span><span class="p">;</span>
</span><span id="__span-31-14"><a id="__codelineno-31-14" name="__codelineno-31-14" href="#__codelineno-31-14"></a><span class="w"> </span><span class="kn">proxy_cache_bypass</span><span class="w"> </span><span class="nv">$http_upgrade</span><span class="p">;</span>
</span><span id="__span-31-15"><a id="__codelineno-31-15" name="__codelineno-31-15" href="#__codelineno-31-15"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-31-16"><a id="__codelineno-31-16" name="__codelineno-31-16" href="#__codelineno-31-16"></a><span class="p">}</span>
</span></code></pre></div>
<ol>
<li><strong>Test direct API access:</strong></li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-32-1"><a id="__codelineno-32-1" name="__codelineno-32-1" href="#__codelineno-32-1"></a><span class="c1"># From host machine</span>
</span><span id="__span-32-2"><a id="__codelineno-32-2" name="__codelineno-32-2" href="#__codelineno-32-2"></a>curl<span class="w"> </span>http://localhost:4100/api/media/videos
</span><span id="__span-32-3"><a id="__codelineno-32-3" name="__codelineno-32-3" href="#__codelineno-32-3"></a>
</span><span id="__span-32-4"><a id="__codelineno-32-4" name="__codelineno-32-4" href="#__codelineno-32-4"></a><span class="c1"># From inside container</span>
</span><span id="__span-32-5"><a id="__codelineno-32-5" name="__codelineno-32-5" href="#__codelineno-32-5"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>media-api<span class="w"> </span>curl<span class="w"> </span>http://localhost:4100/api/media/videos
</span></code></pre></div>
<ol>
<li><strong>Check Docker networking:</strong></li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-33-1"><a id="__codelineno-33-1" name="__codelineno-33-1" href="#__codelineno-33-1"></a>docker<span class="w"> </span>network<span class="w"> </span>inspect<span class="w"> </span>changemaker-lite
</span><span id="__span-33-2"><a id="__codelineno-33-2" name="__codelineno-33-2" href="#__codelineno-33-2"></a><span class="c1"># Verify media-api container connected</span>
</span></code></pre></div>
<hr />
<h3 id="problem-scan-finds-no-videos">Problem: Scan Finds No Videos<a class="headerlink" href="#problem-scan-finds-no-videos" title="Permanent link">&para;</a></h3>
<p><strong>Symptoms:</strong></p>
<ul>
<li>Scan completes with "Created 0 new records"</li>
<li>Directory known to contain video files</li>
<li>Scan reports 0 files scanned</li>
</ul>
<p><strong>Solutions:</strong></p>
<ol>
<li><strong>Verify MEDIA_LIBRARY_PATH correct:</strong></li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-34-1"><a id="__codelineno-34-1" name="__codelineno-34-1" href="#__codelineno-34-1"></a><span class="c1"># Check environment variable</span>
</span><span id="__span-34-2"><a id="__codelineno-34-2" name="__codelineno-34-2" href="#__codelineno-34-2"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>media-api<span class="w"> </span>printenv<span class="w"> </span>MEDIA_LIBRARY_PATH
</span><span id="__span-34-3"><a id="__codelineno-34-3" name="__codelineno-34-3" href="#__codelineno-34-3"></a><span class="c1"># Should output: /media/local/library</span>
</span><span id="__span-34-4"><a id="__codelineno-34-4" name="__codelineno-34-4" href="#__codelineno-34-4"></a>
</span><span id="__span-34-5"><a id="__codelineno-34-5" name="__codelineno-34-5" href="#__codelineno-34-5"></a><span class="c1"># List directory contents</span>
</span><span id="__span-34-6"><a id="__codelineno-34-6" name="__codelineno-34-6" href="#__codelineno-34-6"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>media-api<span class="w"> </span>ls<span class="w"> </span>-la<span class="w"> </span>/media/local/library/videos
</span><span id="__span-34-7"><a id="__codelineno-34-7" name="__codelineno-34-7" href="#__codelineno-34-7"></a><span class="c1"># Should show video files</span>
</span></code></pre></div>
<ol>
<li><strong>Check directory exists:</strong></li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-35-1"><a id="__codelineno-35-1" name="__codelineno-35-1" href="#__codelineno-35-1"></a><span class="c1"># Create missing directory</span>
</span><span id="__span-35-2"><a id="__codelineno-35-2" name="__codelineno-35-2" href="#__codelineno-35-2"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>media-api<span class="w"> </span>mkdir<span class="w"> </span>-p<span class="w"> </span>/media/local/library/videos
</span><span id="__span-35-3"><a id="__codelineno-35-3" name="__codelineno-35-3" href="#__codelineno-35-3"></a>
</span><span id="__span-35-4"><a id="__codelineno-35-4" name="__codelineno-35-4" href="#__codelineno-35-4"></a><span class="c1"># Copy test videos</span>
</span><span id="__span-35-5"><a id="__codelineno-35-5" name="__codelineno-35-5" href="#__codelineno-35-5"></a>docker<span class="w"> </span>cp<span class="w"> </span>test.mp4<span class="w"> </span><span class="k">$(</span>docker<span class="w"> </span>compose<span class="w"> </span>ps<span class="w"> </span>-q<span class="w"> </span>media-api<span class="k">)</span>:/media/local/library/videos/
</span></code></pre></div>
<ol>
<li><strong>Verify Docker volume mounted:</strong></li>
</ol>
<div class="language-yaml highlight"><pre><span></span><code><span id="__span-36-1"><a id="__codelineno-36-1" name="__codelineno-36-1" href="#__codelineno-36-1"></a><span class="c1"># docker-compose.yml</span>
</span><span id="__span-36-2"><a id="__codelineno-36-2" name="__codelineno-36-2" href="#__codelineno-36-2"></a><span class="nt">services</span><span class="p">:</span>
</span><span id="__span-36-3"><a id="__codelineno-36-3" name="__codelineno-36-3" href="#__codelineno-36-3"></a><span class="w"> </span><span class="nt">media-api</span><span class="p">:</span>
</span><span id="__span-36-4"><a id="__codelineno-36-4" name="__codelineno-36-4" href="#__codelineno-36-4"></a><span class="w"> </span><span class="nt">volumes</span><span class="p">:</span>
</span><span id="__span-36-5"><a id="__codelineno-36-5" name="__codelineno-36-5" href="#__codelineno-36-5"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/media/local/library:/media/local/library:ro</span><span class="w"> </span><span class="c1"># Check path correct</span>
</span></code></pre></div>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-37-1"><a id="__codelineno-37-1" name="__codelineno-37-1" href="#__codelineno-37-1"></a><span class="c1"># Inspect volume mounts</span>
</span><span id="__span-37-2"><a id="__codelineno-37-2" name="__codelineno-37-2" href="#__codelineno-37-2"></a>docker<span class="w"> </span>compose<span class="w"> </span>config<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-A<span class="w"> </span><span class="m">5</span><span class="w"> </span>media-api
</span></code></pre></div>
<ol>
<li><strong>Check file extensions supported:</strong></li>
</ol>
<p>Only these extensions scanned:</p>
<ul>
<li><code>.mp4</code></li>
<li><code>.mov</code></li>
<li><code>.avi</code></li>
<li><code>.mkv</code></li>
<li><code>.webm</code></li>
<li><code>.m4v</code></li>
<li><code>.flv</code></li>
</ul>
<p>Rename files if using other extensions:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-38-1"><a id="__codelineno-38-1" name="__codelineno-38-1" href="#__codelineno-38-1"></a><span class="c1"># Rename .MP4 to .mp4 (case-sensitive)</span>
</span><span id="__span-38-2"><a id="__codelineno-38-2" name="__codelineno-38-2" href="#__codelineno-38-2"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>media-api<span class="w"> </span>sh<span class="w"> </span>-c<span class="w"> </span><span class="s1">&#39;cd /media/local/library/videos &amp;&amp; rename &quot;s/.MP4$/.mp4/&quot; *.MP4&#39;</span>
</span></code></pre></div>
<ol>
<li><strong>Check file permissions:</strong></li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-39-1"><a id="__codelineno-39-1" name="__codelineno-39-1" href="#__codelineno-39-1"></a><span class="c1"># Verify readable by container user</span>
</span><span id="__span-39-2"><a id="__codelineno-39-2" name="__codelineno-39-2" href="#__codelineno-39-2"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>media-api<span class="w"> </span>ls<span class="w"> </span>-la<span class="w"> </span>/media/local/library/videos
</span><span id="__span-39-3"><a id="__codelineno-39-3" name="__codelineno-39-3" href="#__codelineno-39-3"></a>
</span><span id="__span-39-4"><a id="__codelineno-39-4" name="__codelineno-39-4" href="#__codelineno-39-4"></a><span class="c1"># Fix permissions if needed (on host)</span>
</span><span id="__span-39-5"><a id="__codelineno-39-5" name="__codelineno-39-5" href="#__codelineno-39-5"></a>sudo<span class="w"> </span>chmod<span class="w"> </span>-R<span class="w"> </span><span class="m">755</span><span class="w"> </span>/media/local/library
</span></code></pre></div>
<hr />
<h3 id="problem-ffprobe-validation-fails">Problem: FFprobe Validation Fails<a class="headerlink" href="#problem-ffprobe-validation-fails" title="Permanent link">&para;</a></h3>
<p><strong>Symptoms:</strong></p>
<ul>
<li>Validation returns error "FFprobe command failed"</li>
<li>Videos marked <code>isValid = false</code></li>
<li>Timeout errors after 30 seconds</li>
</ul>
<p><strong>Solutions:</strong></p>
<ol>
<li><strong>Check FFmpeg installed in container:</strong></li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-40-1"><a id="__codelineno-40-1" name="__codelineno-40-1" href="#__codelineno-40-1"></a><span class="c1"># Verify FFprobe available</span>
</span><span id="__span-40-2"><a id="__codelineno-40-2" name="__codelineno-40-2" href="#__codelineno-40-2"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>media-api<span class="w"> </span>which<span class="w"> </span>ffprobe
</span><span id="__span-40-3"><a id="__codelineno-40-3" name="__codelineno-40-3" href="#__codelineno-40-3"></a><span class="c1"># Should output: /usr/bin/ffprobe</span>
</span><span id="__span-40-4"><a id="__codelineno-40-4" name="__codelineno-40-4" href="#__codelineno-40-4"></a>
</span><span id="__span-40-5"><a id="__codelineno-40-5" name="__codelineno-40-5" href="#__codelineno-40-5"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>media-api<span class="w"> </span>ffprobe<span class="w"> </span>-version
</span><span id="__span-40-6"><a id="__codelineno-40-6" name="__codelineno-40-6" href="#__codelineno-40-6"></a><span class="c1"># Should show FFmpeg version info</span>
</span></code></pre></div>
<ol>
<li><strong>Install FFmpeg if missing:</strong></li>
</ol>
<div class="language-dockerfile highlight"><pre><span></span><code><span id="__span-41-1"><a id="__codelineno-41-1" name="__codelineno-41-1" href="#__codelineno-41-1"></a><span class="c"># api/Dockerfile.media</span>
</span><span id="__span-41-2"><a id="__codelineno-41-2" name="__codelineno-41-2" href="#__codelineno-41-2"></a><span class="k">FROM</span><span class="w"> </span><span class="s">node:20-alpine</span>
</span><span id="__span-41-3"><a id="__codelineno-41-3" name="__codelineno-41-3" href="#__codelineno-41-3"></a>
</span><span id="__span-41-4"><a id="__codelineno-41-4" name="__codelineno-41-4" href="#__codelineno-41-4"></a><span class="c"># Install FFmpeg (both dev and production stages)</span>
</span><span id="__span-41-5"><a id="__codelineno-41-5" name="__codelineno-41-5" href="#__codelineno-41-5"></a><span class="k">RUN</span><span class="w"> </span>apk<span class="w"> </span>add<span class="w"> </span>--no-cache<span class="w"> </span>ffmpeg
</span><span id="__span-41-6"><a id="__codelineno-41-6" name="__codelineno-41-6" href="#__codelineno-41-6"></a>
</span><span id="__span-41-7"><a id="__codelineno-41-7" name="__codelineno-41-7" href="#__codelineno-41-7"></a><span class="c"># ... rest of Dockerfile</span>
</span></code></pre></div>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-42-1"><a id="__codelineno-42-1" name="__codelineno-42-1" href="#__codelineno-42-1"></a><span class="c1"># Rebuild container</span>
</span><span id="__span-42-2"><a id="__codelineno-42-2" name="__codelineno-42-2" href="#__codelineno-42-2"></a>docker<span class="w"> </span>compose<span class="w"> </span>build<span class="w"> </span>media-api
</span><span id="__span-42-3"><a id="__codelineno-42-3" name="__codelineno-42-3" href="#__codelineno-42-3"></a>docker<span class="w"> </span>compose<span class="w"> </span>up<span class="w"> </span>-d<span class="w"> </span>media-api
</span></code></pre></div>
<ol>
<li><strong>Test FFprobe directly on video:</strong></li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-43-1"><a id="__codelineno-43-1" name="__codelineno-43-1" href="#__codelineno-43-1"></a><span class="c1"># Run FFprobe manually</span>
</span><span id="__span-43-2"><a id="__codelineno-43-2" name="__codelineno-43-2" href="#__codelineno-43-2"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>media-api<span class="w"> </span>ffprobe<span class="w"> </span>-v<span class="w"> </span>quiet<span class="w"> </span>-print_format<span class="w"> </span>json<span class="w"> </span>-show_streams<span class="w"> </span>-show_format<span class="w"> </span>/media/local/library/videos/test.mp4
</span><span id="__span-43-3"><a id="__codelineno-43-3" name="__codelineno-43-3" href="#__codelineno-43-3"></a>
</span><span id="__span-43-4"><a id="__codelineno-43-4" name="__codelineno-43-4" href="#__codelineno-43-4"></a><span class="c1"># If this fails, video file corrupt or unsupported</span>
</span></code></pre></div>
<ol>
<li><strong>Check timeout not exceeded:</strong></li>
</ol>
<p>Default timeout: 30 seconds</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-44-1"><a id="__codelineno-44-1" name="__codelineno-44-1" href="#__codelineno-44-1"></a><span class="c1"># For very large files (&gt;5GB), increase timeout</span>
</span><span id="__span-44-2"><a id="__codelineno-44-2" name="__codelineno-44-2" href="#__codelineno-44-2"></a><span class="c1"># api/src/modules/media/services/ffprobe.service.ts</span>
</span><span id="__span-44-3"><a id="__codelineno-44-3" name="__codelineno-44-3" href="#__codelineno-44-3"></a>const<span class="w"> </span><span class="nv">FFPROBE_TIMEOUT</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">60000</span><span class="p">;</span><span class="w"> </span>//<span class="w"> </span><span class="m">60</span><span class="w"> </span>seconds
</span></code></pre></div>
<ol>
<li><strong>Verify video file not corrupt:</strong></li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-45-1"><a id="__codelineno-45-1" name="__codelineno-45-1" href="#__codelineno-45-1"></a><span class="c1"># Test playback</span>
</span><span id="__span-45-2"><a id="__codelineno-45-2" name="__codelineno-45-2" href="#__codelineno-45-2"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>media-api<span class="w"> </span>ffplay<span class="w"> </span>/media/local/library/videos/test.mp4
</span><span id="__span-45-3"><a id="__codelineno-45-3" name="__codelineno-45-3" href="#__codelineno-45-3"></a>
</span><span id="__span-45-4"><a id="__codelineno-45-4" name="__codelineno-45-4" href="#__codelineno-45-4"></a><span class="c1"># Or copy to host and test in VLC</span>
</span><span id="__span-45-5"><a id="__codelineno-45-5" name="__codelineno-45-5" href="#__codelineno-45-5"></a>docker<span class="w"> </span>cp<span class="w"> </span><span class="k">$(</span>docker<span class="w"> </span>compose<span class="w"> </span>ps<span class="w"> </span>-q<span class="w"> </span>media-api<span class="k">)</span>:/media/local/library/videos/test.mp4<span class="w"> </span>./test.mp4
</span><span id="__span-45-6"><a id="__codelineno-45-6" name="__codelineno-45-6" href="#__codelineno-45-6"></a>vlc<span class="w"> </span>test.mp4
</span></code></pre></div>
<ol>
<li><strong>Check for special characters in filename:</strong></li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-46-1"><a id="__codelineno-46-1" name="__codelineno-46-1" href="#__codelineno-46-1"></a><span class="c1"># Rename files with spaces or special chars</span>
</span><span id="__span-46-2"><a id="__codelineno-46-2" name="__codelineno-46-2" href="#__codelineno-46-2"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>media-api<span class="w"> </span>sh<span class="w"> </span>-c<span class="w"> </span><span class="s1">&#39;cd /media/local/library/videos &amp;&amp; rename &quot;s/ /_/g&quot; *.mp4&#39;</span>
</span></code></pre></div>
<hr />
<h3 id="problem-drizzle-schema-changes-not-applied">Problem: Drizzle Schema Changes Not Applied<a class="headerlink" href="#problem-drizzle-schema-changes-not-applied" title="Permanent link">&para;</a></h3>
<p><strong>Symptoms:</strong></p>
<ul>
<li>Code references new column but database doesn't have it</li>
<li>Error: "column does not exist"</li>
<li>Schema changes made but not reflected</li>
</ul>
<p><strong>Solutions:</strong></p>
<ol>
<li><strong>Push schema changes:</strong></li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-47-1"><a id="__codelineno-47-1" name="__codelineno-47-1" href="#__codelineno-47-1"></a><span class="c1"># Drizzle uses push (not migrations)</span>
</span><span id="__span-47-2"><a id="__codelineno-47-2" name="__codelineno-47-2" href="#__codelineno-47-2"></a><span class="nb">cd</span><span class="w"> </span>api
</span><span id="__span-47-3"><a id="__codelineno-47-3" name="__codelineno-47-3" href="#__codelineno-47-3"></a>npx<span class="w"> </span>drizzle-kit<span class="w"> </span>push
</span><span id="__span-47-4"><a id="__codelineno-47-4" name="__codelineno-47-4" href="#__codelineno-47-4"></a>
</span><span id="__span-47-5"><a id="__codelineno-47-5" name="__codelineno-47-5" href="#__codelineno-47-5"></a><span class="c1"># Confirm changes</span>
</span></code></pre></div>
<ol>
<li><strong>Verify connection:</strong></li>
</ol>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-48-1"><a id="__codelineno-48-1" name="__codelineno-48-1" href="#__codelineno-48-1"></a><span class="c1"># Check DATABASE_URL correct</span>
</span><span id="__span-48-2"><a id="__codelineno-48-2" name="__codelineno-48-2" href="#__codelineno-48-2"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>media-api<span class="w"> </span>printenv<span class="w"> </span>DATABASE_URL
</span><span id="__span-48-3"><a id="__codelineno-48-3" name="__codelineno-48-3" href="#__codelineno-48-3"></a>
</span><span id="__span-48-4"><a id="__codelineno-48-4" name="__codelineno-48-4" href="#__codelineno-48-4"></a><span class="c1"># Test connection</span>
</span><span id="__span-48-5"><a id="__codelineno-48-5" name="__codelineno-48-5" href="#__codelineno-48-5"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>media-api<span class="w"> </span>npx<span class="w"> </span>drizzle-kit<span class="w"> </span>studio
</span><span id="__span-48-6"><a id="__codelineno-48-6" name="__codelineno-48-6" href="#__codelineno-48-6"></a><span class="c1"># Opens DB browser on http://localhost:4983</span>
</span></code></pre></div>
<ol>
<li><strong>Compare with Prisma migrations:</strong></li>
</ol>
<p>Media tables exist in same database as Prisma tables. If conflict:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-49-1"><a id="__codelineno-49-1" name="__codelineno-49-1" href="#__codelineno-49-1"></a><span class="c1"># Check both schemas</span>
</span><span id="__span-49-2"><a id="__codelineno-49-2" name="__codelineno-49-2" href="#__codelineno-49-2"></a>npx<span class="w"> </span>prisma<span class="w"> </span>db<span class="w"> </span>pull<span class="w"> </span><span class="c1"># Prisma introspection</span>
</span><span id="__span-49-3"><a id="__codelineno-49-3" name="__codelineno-49-3" href="#__codelineno-49-3"></a>npx<span class="w"> </span>drizzle-kit<span class="w"> </span>introspect<span class="w"> </span><span class="c1"># Drizzle introspection</span>
</span><span id="__span-49-4"><a id="__codelineno-49-4" name="__codelineno-49-4" href="#__codelineno-49-4"></a>
</span><span id="__span-49-5"><a id="__codelineno-49-5" name="__codelineno-49-5" href="#__codelineno-49-5"></a><span class="c1"># Resolve conflicts manually</span>
</span></code></pre></div>
<hr />
<h3 id="problem-large-library-performance">Problem: Large Library Performance<a class="headerlink" href="#problem-large-library-performance" title="Permanent link">&para;</a></h3>
<p><strong>Symptoms:</strong></p>
<ul>
<li>Library page loads slowly (5+ seconds)</li>
<li>Pagination sluggish</li>
<li>Scan operations timeout</li>
</ul>
<p><strong>Solutions:</strong></p>
<ol>
<li><strong>Add database indexes:</strong></li>
</ol>
<div class="language-sql highlight"><pre><span></span><code><span id="__span-50-1"><a id="__codelineno-50-1" name="__codelineno-50-1" href="#__codelineno-50-1"></a><span class="c1">-- Index for common filters</span>
</span><span id="__span-50-2"><a id="__codelineno-50-2" name="__codelineno-50-2" href="#__codelineno-50-2"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_videos_directory_type</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">videos</span><span class="p">(</span><span class="n">directory_type</span><span class="p">);</span>
</span><span id="__span-50-3"><a id="__codelineno-50-3" name="__codelineno-50-3" href="#__codelineno-50-3"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_videos_orientation</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">videos</span><span class="p">(</span><span class="n">orientation</span><span class="p">);</span>
</span><span id="__span-50-4"><a id="__codelineno-50-4" name="__codelineno-50-4" href="#__codelineno-50-4"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_videos_quality</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">videos</span><span class="p">(</span><span class="n">quality</span><span class="p">);</span>
</span><span id="__span-50-5"><a id="__codelineno-50-5" name="__codelineno-50-5" href="#__codelineno-50-5"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_videos_is_valid</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">videos</span><span class="p">(</span><span class="n">is_valid</span><span class="p">);</span>
</span><span id="__span-50-6"><a id="__codelineno-50-6" name="__codelineno-50-6" href="#__codelineno-50-6"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_videos_created_at</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">videos</span><span class="p">(</span><span class="n">created_at</span><span class="w"> </span><span class="k">DESC</span><span class="p">);</span>
</span><span id="__span-50-7"><a id="__codelineno-50-7" name="__codelineno-50-7" href="#__codelineno-50-7"></a>
</span><span id="__span-50-8"><a id="__codelineno-50-8" name="__codelineno-50-8" href="#__codelineno-50-8"></a><span class="c1">-- Composite index for filtered queries</span>
</span><span id="__span-50-9"><a id="__codelineno-50-9" name="__codelineno-50-9" href="#__codelineno-50-9"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_videos_filters</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">videos</span><span class="p">(</span><span class="n">directory_type</span><span class="p">,</span><span class="w"> </span><span class="n">is_valid</span><span class="p">,</span><span class="w"> </span><span class="n">created_at</span><span class="w"> </span><span class="k">DESC</span><span class="p">);</span>
</span><span id="__span-50-10"><a id="__codelineno-50-10" name="__codelineno-50-10" href="#__codelineno-50-10"></a>
</span><span id="__span-50-11"><a id="__codelineno-50-11" name="__codelineno-50-11" href="#__codelineno-50-11"></a><span class="c1">-- Full-text search index</span>
</span><span id="__span-50-12"><a id="__codelineno-50-12" name="__codelineno-50-12" href="#__codelineno-50-12"></a><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_videos_search</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">videos</span><span class="w"> </span><span class="k">USING</span><span class="w"> </span><span class="n">gin</span><span class="p">(</span><span class="n">to_tsvector</span><span class="p">(</span><span class="s1">&#39;english&#39;</span><span class="p">,</span><span class="w"> </span><span class="k">coalesce</span><span class="p">(</span><span class="n">title</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="p">)</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="s1">&#39; &#39;</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="k">coalesce</span><span class="p">(</span><span class="n">producer</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="p">)</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="s1">&#39; &#39;</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="k">coalesce</span><span class="p">(</span><span class="n">creator</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="p">)));</span>
</span></code></pre></div>
<ol>
<li><strong>Reduce page size:</strong></li>
</ol>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-51-1"><a id="__codelineno-51-1" name="__codelineno-51-1" href="#__codelineno-51-1"></a><span class="c1">// admin/src/pages/media/LibraryPage.tsx</span>
</span><span id="__span-51-2"><a id="__codelineno-51-2" name="__codelineno-51-2" href="#__codelineno-51-2"></a><span class="kd">const</span><span class="w"> </span><span class="p">[</span><span class="nx">pagination</span><span class="p">,</span><span class="w"> </span><span class="nx">setPagination</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">useState</span><span class="p">({</span><span class="w"> </span><span class="nx">page</span><span class="o">:</span><span class="w"> </span><span class="kt">1</span><span class="p">,</span><span class="w"> </span><span class="nx">limit</span><span class="o">:</span><span class="w"> </span><span class="kt">10</span><span class="p">,</span><span class="w"> </span><span class="nx">total</span><span class="o">:</span><span class="w"> </span><span class="kt">0</span><span class="w"> </span><span class="p">});</span>
</span><span id="__span-51-3"><a id="__codelineno-51-3" name="__codelineno-51-3" href="#__codelineno-51-3"></a><span class="c1">// Reduced from 20 to 10</span>
</span></code></pre></div>
<ol>
<li><strong>Enable query caching:</strong></li>
</ol>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-52-1"><a id="__codelineno-52-1" name="__codelineno-52-1" href="#__codelineno-52-1"></a><span class="c1">// api/src/modules/media/routes/videos.routes.ts</span>
</span><span id="__span-52-2"><a id="__codelineno-52-2" name="__codelineno-52-2" href="#__codelineno-52-2"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">redisClient</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;@/config/redis&#39;</span><span class="p">;</span>
</span><span id="__span-52-3"><a id="__codelineno-52-3" name="__codelineno-52-3" href="#__codelineno-52-3"></a>
</span><span id="__span-52-4"><a id="__codelineno-52-4" name="__codelineno-52-4" href="#__codelineno-52-4"></a><span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;/api/media/videos&#39;</span><span class="p">,</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="p">(</span><span class="nx">req</span><span class="p">,</span><span class="w"> </span><span class="nx">reply</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-52-5"><a id="__codelineno-52-5" name="__codelineno-52-5" href="#__codelineno-52-5"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">cacheKey</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sb">`videos:list:</span><span class="si">${</span><span class="nb">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">query</span><span class="p">)</span><span class="si">}</span><span class="sb">`</span><span class="p">;</span>
</span><span id="__span-52-6"><a id="__codelineno-52-6" name="__codelineno-52-6" href="#__codelineno-52-6"></a>
</span><span id="__span-52-7"><a id="__codelineno-52-7" name="__codelineno-52-7" href="#__codelineno-52-7"></a><span class="w"> </span><span class="c1">// Check cache</span>
</span><span id="__span-52-8"><a id="__codelineno-52-8" name="__codelineno-52-8" href="#__codelineno-52-8"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">cached</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">redisClient</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="nx">cacheKey</span><span class="p">);</span>
</span><span id="__span-52-9"><a id="__codelineno-52-9" name="__codelineno-52-9" href="#__codelineno-52-9"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">cached</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-52-10"><a id="__codelineno-52-10" name="__codelineno-52-10" href="#__codelineno-52-10"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">reply</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nb">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">cached</span><span class="p">));</span>
</span><span id="__span-52-11"><a id="__codelineno-52-11" name="__codelineno-52-11" href="#__codelineno-52-11"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-52-12"><a id="__codelineno-52-12" name="__codelineno-52-12" href="#__codelineno-52-12"></a>
</span><span id="__span-52-13"><a id="__codelineno-52-13" name="__codelineno-52-13" href="#__codelineno-52-13"></a><span class="w"> </span><span class="c1">// Fetch from database</span>
</span><span id="__span-52-14"><a id="__codelineno-52-14" name="__codelineno-52-14" href="#__codelineno-52-14"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">results</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">db</span><span class="p">.</span><span class="nx">select</span><span class="p">()...;</span>
</span><span id="__span-52-15"><a id="__codelineno-52-15" name="__codelineno-52-15" href="#__codelineno-52-15"></a>
</span><span id="__span-52-16"><a id="__codelineno-52-16" name="__codelineno-52-16" href="#__codelineno-52-16"></a><span class="w"> </span><span class="c1">// Cache for 5 minutes</span>
</span><span id="__span-52-17"><a id="__codelineno-52-17" name="__codelineno-52-17" href="#__codelineno-52-17"></a><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">redisClient</span><span class="p">.</span><span class="nx">setex</span><span class="p">(</span><span class="nx">cacheKey</span><span class="p">,</span><span class="w"> </span><span class="mf">300</span><span class="p">,</span><span class="w"> </span><span class="nb">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">results</span><span class="p">));</span>
</span><span id="__span-52-18"><a id="__codelineno-52-18" name="__codelineno-52-18" href="#__codelineno-52-18"></a>
</span><span id="__span-52-19"><a id="__codelineno-52-19" name="__codelineno-52-19" href="#__codelineno-52-19"></a><span class="w"> </span><span class="nx">reply</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">results</span><span class="p">);</span>
</span><span id="__span-52-20"><a id="__codelineno-52-20" name="__codelineno-52-20" href="#__codelineno-52-20"></a><span class="p">});</span>
</span></code></pre></div>
<ol>
<li><strong>Use virtual scrolling:</strong></li>
</ol>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-53-1"><a id="__codelineno-53-1" name="__codelineno-53-1" href="#__codelineno-53-1"></a><span class="c1">// Replace Ant Design Table with react-window for large datasets</span>
</span><span id="__span-53-2"><a id="__codelineno-53-2" name="__codelineno-53-2" href="#__codelineno-53-2"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">FixedSizeList</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;react-window&#39;</span><span class="p">;</span>
</span></code></pre></div>
<hr />
<h2 id="performance-considerations">Performance Considerations<a class="headerlink" href="#performance-considerations" title="Permanent link">&para;</a></h2>
<h3 id="directory-scans">Directory Scans<a class="headerlink" href="#directory-scans" title="Permanent link">&para;</a></h3>
<p><strong>Scaling Factors:</strong></p>
<ul>
<li>100 files: ~2 seconds</li>
<li>1,000 files: ~15 seconds</li>
<li>10,000 files: ~2.5 minutes</li>
</ul>
<p><strong>Optimization Strategies:</strong></p>
<ol>
<li><strong>Incremental Scans</strong> — Use <code>skipExisting: true</code> to only process new files</li>
<li><strong>Parallel Processing</strong> — Scan multiple directories simultaneously</li>
<li><strong>Background Jobs</strong> — Queue scans as async jobs instead of synchronous requests</li>
<li><strong>Caching</strong> — Cache directory listings in Redis</li>
</ol>
<h3 id="ffprobe-extraction">FFprobe Extraction<a class="headerlink" href="#ffprobe-extraction" title="Permanent link">&para;</a></h3>
<p><strong>Timing:</strong></p>
<ul>
<li>Small video (&lt;100MB): ~50-100ms</li>
<li>Medium video (500MB): ~150-250ms</li>
<li>Large video (2GB+): ~500ms-1s</li>
</ul>
<p><strong>Batch Processing:</strong></p>
<p>For 100 videos: ~10-20 seconds total</p>
<p><strong>Optimization:</strong></p>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-54-1"><a id="__codelineno-54-1" name="__codelineno-54-1" href="#__codelineno-54-1"></a><span class="c1">// Parallel extraction (limit concurrency)</span>
</span><span id="__span-54-2"><a id="__codelineno-54-2" name="__codelineno-54-2" href="#__codelineno-54-2"></a><span class="k">import</span><span class="w"> </span><span class="nx">pLimit</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;p-limit&#39;</span><span class="p">;</span>
</span><span id="__span-54-3"><a id="__codelineno-54-3" name="__codelineno-54-3" href="#__codelineno-54-3"></a>
</span><span id="__span-54-4"><a id="__codelineno-54-4" name="__codelineno-54-4" href="#__codelineno-54-4"></a><span class="kd">const</span><span class="w"> </span><span class="nx">limit</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">pLimit</span><span class="p">(</span><span class="mf">5</span><span class="p">);</span><span class="w"> </span><span class="c1">// Max 5 concurrent FFprobe calls</span>
</span><span id="__span-54-5"><a id="__codelineno-54-5" name="__codelineno-54-5" href="#__codelineno-54-5"></a>
</span><span id="__span-54-6"><a id="__codelineno-54-6" name="__codelineno-54-6" href="#__codelineno-54-6"></a><span class="kd">const</span><span class="w"> </span><span class="nx">results</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">(</span>
</span><span id="__span-54-7"><a id="__codelineno-54-7" name="__codelineno-54-7" href="#__codelineno-54-7"></a><span class="w"> </span><span class="nx">videoFiles</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">file</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span>
</span><span id="__span-54-8"><a id="__codelineno-54-8" name="__codelineno-54-8" href="#__codelineno-54-8"></a><span class="w"> </span><span class="nx">limit</span><span class="p">(()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">ffprobeService</span><span class="p">.</span><span class="nx">extract</span><span class="p">(</span><span class="nx">file</span><span class="p">))</span>
</span><span id="__span-54-9"><a id="__codelineno-54-9" name="__codelineno-54-9" href="#__codelineno-54-9"></a><span class="w"> </span><span class="p">)</span>
</span><span id="__span-54-10"><a id="__codelineno-54-10" name="__codelineno-54-10" href="#__codelineno-54-10"></a><span class="p">);</span>
</span></code></pre></div>
<h3 id="database-queries">Database Queries<a class="headerlink" href="#database-queries" title="Permanent link">&para;</a></h3>
<p><strong>Query Performance:</strong></p>
<ul>
<li>List 20 videos (no filters): ~5-10ms</li>
<li>List 20 videos (with filters): ~10-20ms</li>
<li>Full-text search: ~20-50ms</li>
<li>Count total videos: ~5ms (with index)</li>
</ul>
<p><strong>Optimization:</strong></p>
<ol>
<li><strong>Always use pagination</strong> — Never fetch all records</li>
<li><strong>Index heavily filtered columns</strong> — directoryType, orientation, quality, isValid</li>
<li><strong>Use SELECT only needed columns</strong> — Avoid <code>SELECT *</code> for large tables</li>
<li><strong>Cache counts</strong> — Total video count changes infrequently, cache in Redis</li>
</ol>
<h3 id="thumbnail-generation">Thumbnail Generation<a class="headerlink" href="#thumbnail-generation" title="Permanent link">&para;</a></h3>
<p><strong>Deferred Loading:</strong></p>
<p>Don't generate thumbnails during scan. Instead:</p>
<ol>
<li>Create video record without thumbnail</li>
<li>Queue thumbnail generation job</li>
<li>Worker processes job asynchronously</li>
<li>Update record with <code>thumbnailPath</code></li>
</ol>
<p><strong>Lazy Loading:</strong></p>
<p>Frontend requests thumbnails only when visible (IntersectionObserver).</p>
<hr />
<h2 id="dual-api-architecture">Dual API Architecture<a class="headerlink" href="#dual-api-architecture" title="Permanent link">&para;</a></h2>
<h3 id="why-separate-fastify-api">Why Separate Fastify API?<a class="headerlink" href="#why-separate-fastify-api" title="Permanent link">&para;</a></h3>
<p>The media system was introduced as a <strong>Phase 14 enhancement</strong> after V2 core functionality stabilized. A separate Fastify microservice was chosen to:</p>
<ol>
<li><strong>Avoid Disrupting Stable Express API</strong> — V2 Express API battle-tested with 30+ models, introducing media directly risked regressions</li>
<li><strong>Test Drizzle ORM Migration</strong> — Fastify+Drizzle serves as proof-of-concept for potential future Prisma→Drizzle migration</li>
<li><strong>Isolate Video Processing</strong> — CPU/GPU-intensive FFprobe, encoding jobs isolated from main API request handling</li>
<li><strong>Independent Scaling</strong> — Media API can be horizontally scaled separately based on video processing load</li>
<li><strong>Technology Experimentation</strong> — Fastify's performance benefits evaluated for potential broader adoption</li>
</ol>
<h3 id="database-sharing-strategy">Database Sharing Strategy<a class="headerlink" href="#database-sharing-strategy" title="Permanent link">&para;</a></h3>
<p><strong>Same PostgreSQL, Different ORMs:</strong></p>
<div class="language-text highlight"><pre><span></span><code><span id="__span-55-1"><a id="__codelineno-55-1" name="__codelineno-55-1" href="#__codelineno-55-1"></a>┌─────────────────┐
</span><span id="__span-55-2"><a id="__codelineno-55-2" name="__codelineno-55-2" href="#__codelineno-55-2"></a>│ PostgreSQL 16 │
</span><span id="__span-55-3"><a id="__codelineno-55-3" name="__codelineno-55-3" href="#__codelineno-55-3"></a>│ v2_changemaker │
</span><span id="__span-55-4"><a id="__codelineno-55-4" name="__codelineno-55-4" href="#__codelineno-55-4"></a>└─────────────────┘
</span><span id="__span-55-5"><a id="__codelineno-55-5" name="__codelineno-55-5" href="#__codelineno-55-5"></a>
</span><span id="__span-55-6"><a id="__codelineno-55-6" name="__codelineno-55-6" href="#__codelineno-55-6"></a> ┌────┴────┐
</span><span id="__span-55-7"><a id="__codelineno-55-7" name="__codelineno-55-7" href="#__codelineno-55-7"></a> │ │
</span><span id="__span-55-8"><a id="__codelineno-55-8" name="__codelineno-55-8" href="#__codelineno-55-8"></a>┌───┴───┐ ┌──┴────┐
</span><span id="__span-55-9"><a id="__codelineno-55-9" name="__codelineno-55-9" href="#__codelineno-55-9"></a>│Prisma │ │Drizzle│
</span><span id="__span-55-10"><a id="__codelineno-55-10" name="__codelineno-55-10" href="#__codelineno-55-10"></a>│ ORM │ │ ORM │
</span><span id="__span-55-11"><a id="__codelineno-55-11" name="__codelineno-55-11" href="#__codelineno-55-11"></a>└───┬───┘ └──┬────┘
</span><span id="__span-55-12"><a id="__codelineno-55-12" name="__codelineno-55-12" href="#__codelineno-55-12"></a> │ │
</span><span id="__span-55-13"><a id="__codelineno-55-13" name="__codelineno-55-13" href="#__codelineno-55-13"></a>┌───┴────┐ ┌─┴─────┐
</span><span id="__span-55-14"><a id="__codelineno-55-14" name="__codelineno-55-14" href="#__codelineno-55-14"></a>│Express │ │Fastify│
</span><span id="__span-55-15"><a id="__codelineno-55-15" name="__codelineno-55-15" href="#__codelineno-55-15"></a>│ API │ │ Media │
</span><span id="__span-55-16"><a id="__codelineno-55-16" name="__codelineno-55-16" href="#__codelineno-55-16"></a>│ :4000 │ │ API │
</span><span id="__span-55-17"><a id="__codelineno-55-17" name="__codelineno-55-17" href="#__codelineno-55-17"></a>│ │ │ :4100 │
</span><span id="__span-55-18"><a id="__codelineno-55-18" name="__codelineno-55-18" href="#__codelineno-55-18"></a>└────────┘ └───────┘
</span></code></pre></div>
<p><strong>Benefits:</strong></p>
<ul>
<li><strong>Single Source of Truth</strong> — All data in one database</li>
<li><strong>Cross-API Queries</strong> — Main API can query media tables via Prisma raw queries</li>
<li><strong>Unified Backups</strong> — One PostgreSQL dump includes both APIs</li>
<li><strong>Shared Connections</strong> — Connection pooling optimizations benefit both</li>
</ul>
<p><strong>Challenges:</strong></p>
<ul>
<li><strong>Schema Coordination</strong> — Must manually sync schema changes between Prisma migrations and Drizzle pushes</li>
<li><strong>Type Conflicts</strong> — Same table, different type definitions (Prisma vs Drizzle types)</li>
<li><strong>Migration Complexity</strong> — Prisma generates migrations, Drizzle uses push (no migration files)</li>
</ul>
<h3 id="migration-strategy-roadmap">Migration Strategy Roadmap<a class="headerlink" href="#migration-strategy-roadmap" title="Permanent link">&para;</a></h3>
<p><strong>Short Term (Current):</strong></p>
<ul>
<li>Keep dual API architecture</li>
<li>Synchronize schemas manually</li>
<li>Document shared tables in both ORMs</li>
</ul>
<p><strong>Medium Term (6-12 months):</strong></p>
<ul>
<li>Evaluate Fastify+Drizzle performance vs Express+Prisma</li>
<li>If Fastify superior, migrate select Express routes to Fastify</li>
<li>If no significant benefit, consolidate media into Express+Prisma</li>
</ul>
<p><strong>Long Term (12+ months):</strong></p>
<ul>
<li>Unified API (either all Express or all Fastify)</li>
<li>Single ORM (either all Prisma or all Drizzle)</li>
<li>Deprecate less performant stack</li>
</ul>
<p><strong>Migration Effort Estimate:</strong></p>
<ul>
<li><strong>Media to Express+Prisma:</strong> 3-5 days (convert Drizzle queries to Prisma, merge Fastify routes into Express)</li>
<li><strong>All to Fastify+Drizzle:</strong> 2-3 weeks (convert 30+ Prisma models to Drizzle, rewrite Express routes for Fastify)</li>
</ul>
<hr />
<h2 id="related-documentation">Related Documentation<a class="headerlink" href="#related-documentation" title="Permanent link">&para;</a></h2>
<h3 id="backend-documentation">Backend Documentation<a class="headerlink" href="#backend-documentation" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>API Server:</strong> <code>backend/api/media-server.md</code> — Fastify server setup, middleware, error handling</li>
<li><strong>Videos Module:</strong> <code>backend/modules/media/videos.md</code> — Video routes, service layer, business logic</li>
<li><strong>FFprobe Service:</strong> <code>backend/modules/media/ffprobe.md</code> — Metadata extraction implementation</li>
<li><strong>Jobs System:</strong> <code>backend/modules/media/jobs.md</code> — Job queue architecture, worker processes</li>
</ul>
<h3 id="frontend-documentation">Frontend Documentation<a class="headerlink" href="#frontend-documentation" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>Library Page:</strong> <code>frontend/pages/media/library.md</code> — Video library management UI</li>
<li><strong>Shared Media Page:</strong> <code>frontend/pages/media/shared.md</code> — Public gallery admin UI</li>
<li><strong>Media Components:</strong> <code>frontend/components/media.md</code> — Reusable video components</li>
</ul>
<h3 id="database-documentation">Database Documentation<a class="headerlink" href="#database-documentation" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>Media Models:</strong> <code>database/models/media.md</code> — Drizzle schema definitions for videos, compilations, jobs</li>
<li><strong>Drizzle Setup:</strong> <code>database/drizzle.md</code> — Drizzle ORM configuration, connection management</li>
</ul>
<h3 id="feature-documentation">Feature Documentation<a class="headerlink" href="#feature-documentation" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>Video Upload:</strong> <code>features/media/upload.md</code> — Upload system workflow, FFprobe integration</li>
<li><strong>Media Jobs:</strong> <code>features/media/jobs.md</code> — Job queue system, processing pipeline</li>
<li><strong>Public Gallery:</strong> <code>features/media/public-gallery.md</code> — Public video sharing system</li>
</ul>
<h3 id="integration-documentation">Integration Documentation<a class="headerlink" href="#integration-documentation" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>Dual API Architecture:</strong> <code>architecture/dual-api.md</code> — Express+Prisma vs Fastify+Drizzle comparison</li>
<li><strong>Nginx Routing:</strong> <code>deployment/nginx.md</code> — Reverse proxy configuration for media.cmlite.org</li>
<li><strong>Docker Setup:</strong> <code>deployment/docker.md</code> — Media API container, volume mounts, healthchecks</li>
</ul>
<hr />
<h2 id="next-steps">Next Steps<a class="headerlink" href="#next-steps" title="Permanent link">&para;</a></h2>
<p>After mastering video library management:</p>
<ol>
<li><strong>Upload System</strong> — Read <code>features/media/upload.md</code> to understand video upload workflow</li>
<li><strong>Jobs Queue</strong> — Review <code>features/media/jobs.md</code> for video processing automation</li>
<li><strong>Public Gallery</strong> — Explore <code>features/media/public-gallery.md</code> for sharing videos publicly</li>
<li><strong>Custom Integrations</strong> — Use Media API endpoints to build custom video features</li>
</ol>
<p>For hands-on practice, try:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-56-1"><a id="__codelineno-56-1" name="__codelineno-56-1" href="#__codelineno-56-1"></a><span class="c1"># 1. Upload test videos</span>
</span><span id="__span-56-2"><a id="__codelineno-56-2" name="__codelineno-56-2" href="#__codelineno-56-2"></a>curl<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span>http://localhost:4100/api/media/upload/single<span class="w"> </span><span class="se">\</span>
</span><span id="__span-56-3"><a id="__codelineno-56-3" name="__codelineno-56-3" href="#__codelineno-56-3"></a><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Authorization: Bearer YOUR_TOKEN&quot;</span><span class="w"> </span><span class="se">\</span>
</span><span id="__span-56-4"><a id="__codelineno-56-4" name="__codelineno-56-4" href="#__codelineno-56-4"></a><span class="w"> </span>-F<span class="w"> </span><span class="s2">&quot;video=@test.mp4&quot;</span><span class="w"> </span><span class="se">\</span>
</span><span id="__span-56-5"><a id="__codelineno-56-5" name="__codelineno-56-5" href="#__codelineno-56-5"></a><span class="w"> </span>-F<span class="w"> </span><span class="s2">&quot;producer=Test Studio&quot;</span><span class="w"> </span><span class="se">\</span>
</span><span id="__span-56-6"><a id="__codelineno-56-6" name="__codelineno-56-6" href="#__codelineno-56-6"></a><span class="w"> </span>-F<span class="w"> </span><span class="s2">&quot;title=Test Video&quot;</span>
</span><span id="__span-56-7"><a id="__codelineno-56-7" name="__codelineno-56-7" href="#__codelineno-56-7"></a>
</span><span id="__span-56-8"><a id="__codelineno-56-8" name="__codelineno-56-8" href="#__codelineno-56-8"></a><span class="c1"># 2. Scan directory</span>
</span><span id="__span-56-9"><a id="__codelineno-56-9" name="__codelineno-56-9" href="#__codelineno-56-9"></a>curl<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span>http://localhost:4100/api/media/videos/scan<span class="w"> </span><span class="se">\</span>
</span><span id="__span-56-10"><a id="__codelineno-56-10" name="__codelineno-56-10" href="#__codelineno-56-10"></a><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Authorization: Bearer YOUR_TOKEN&quot;</span><span class="w"> </span><span class="se">\</span>
</span><span id="__span-56-11"><a id="__codelineno-56-11" name="__codelineno-56-11" href="#__codelineno-56-11"></a><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Content-Type: application/json&quot;</span><span class="w"> </span><span class="se">\</span>
</span><span id="__span-56-12"><a id="__codelineno-56-12" name="__codelineno-56-12" href="#__codelineno-56-12"></a><span class="w"> </span>-d<span class="w"> </span><span class="s1">&#39;{&quot;directoryType&quot;: &quot;videos&quot;}&#39;</span>
</span><span id="__span-56-13"><a id="__codelineno-56-13" name="__codelineno-56-13" href="#__codelineno-56-13"></a>
</span><span id="__span-56-14"><a id="__codelineno-56-14" name="__codelineno-56-14" href="#__codelineno-56-14"></a><span class="c1"># 3. List videos</span>
</span><span id="__span-56-15"><a id="__codelineno-56-15" name="__codelineno-56-15" href="#__codelineno-56-15"></a>curl<span class="w"> </span>http://localhost:4100/api/media/videos?page<span class="o">=</span><span class="m">1</span><span class="p">&amp;</span><span class="nv">limit</span><span class="o">=</span><span class="m">10</span>
</span><span id="__span-56-16"><a id="__codelineno-56-16" name="__codelineno-56-16" href="#__codelineno-56-16"></a>
</span><span id="__span-56-17"><a id="__codelineno-56-17" name="__codelineno-56-17" href="#__codelineno-56-17"></a><span class="c1"># 4. Validate video</span>
</span><span id="__span-56-18"><a id="__codelineno-56-18" name="__codelineno-56-18" href="#__codelineno-56-18"></a>curl<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span>http://localhost:4100/api/media/videos/VIDEO_ID/validate<span class="w"> </span><span class="se">\</span>
</span><span id="__span-56-19"><a id="__codelineno-56-19" name="__codelineno-56-19" href="#__codelineno-56-19"></a><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Authorization: Bearer YOUR_TOKEN&quot;</span>
</span></code></pre></div>
<hr />
<p><strong>Last Updated:</strong> 2026-02-13
<strong>Version:</strong> V2.0
<strong>Maintainer:</strong> Changemaker Lite Team</p>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<nav class="md-footer__inner md-grid" aria-label="Footer" >
<a href="../" class="md-footer__link md-footer__link--prev" aria-label="Previous: Media Manager">
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</div>
<div class="md-footer__title">
<span class="md-footer__direction">
Previous
</span>
<div class="md-ellipsis">
Media Manager
</div>
</div>
</a>
<a href="../upload/" class="md-footer__link md-footer__link--next" aria-label="Next: Upload System">
<div class="md-footer__title">
<span class="md-footer__direction">
Next
</span>
<div class="md-ellipsis">
Upload System
</div>
</div>
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 11v2h12l-5.5 5.5 1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5 16 11z"/></svg>
</div>
</a>
</nav>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
<div class="md-copyright__highlight">
Copyright &copy; 2024 The Bunker Operations <a href="#__consent">Change cookie settings</a>
</div>
</div>
<div class="md-social">
<a href="https://gitea.bnkops.com/admin" target="_blank" rel="noopener" title="Gitea Repository" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M173.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6m-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3m44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9M252.8 8C114.1 8 8 113.3 8 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C436.2 457.8 504 362.9 504 252 504 113.3 391.5 8 252.8 8M105.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1m-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7m32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1m-11.4-14.7c-1.6 1-1.6 3.6 0 5.9s4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2"/></svg>
</a>
<a href="https://listmonk.bnkops.com/subscription/form" target="_blank" rel="noopener" title="Newsletter" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M536.4-26.3c9.8-3.5 20.6-1 28 6.3s9.8 18.2 6.3 28l-178 496.9c-5 13.9-18.1 23.1-32.8 23.1-14.2 0-27-8.6-32.3-21.7l-64.2-158c-4.5-11-2.5-23.6 5.2-32.6l94.5-112.4c5.1-6.1 4.7-15-.9-20.6s-14.6-6-20.6-.9l-112.4 94.3c-9.1 7.6-21.6 9.6-32.6 5.2L38.1 216.8c-13.1-5.3-21.7-18.1-21.7-32.3 0-14.7 9.2-27.8 23.1-32.8z"/></svg>
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"annotate": null, "base": "../../../..", "features": ["announce.dismiss", "content.action.edit", "content.action.view", "content.code.annotate", "content.code.copy", "content.tooltips", "navigation.expand", "navigation.footer", "navigation.indexes", "navigation.path", "navigation.prune", "navigation.sections", "navigation.tabs", "navigation.tabs.sticky", "navigation.top", "navigation.tracking", "search.highlight", "search.share", "search.suggest", "toc.follow"], "search": "../../../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../../../../assets/javascripts/bundle.79ae519e.min.js"></script>
<script src="../../../../javascripts/home.js"></script>
<script src="../../../../javascripts/github-widget.js"></script>
<script src="../../../../javascripts/gitea-widget.js"></script>
<script src="../../../../assets/js/env-config.js"></script>
<script src="../../../../assets/js/video-player.js"></script>
</body>
</html>