The manual · everything, in order

How ARCH‑ANGEL works.

Install it, run your first clip, then wire up the parts you actually need. Written to be read start-to-finish once, and skimmed forever after.

press / to search hover a code block to copy printable

What it is

ARCH‑ANGEL takes a long video — a podcast, a stream VOD, a lecture, a local file — and produces vertical short-form clips from the moments most likely to hold attention: fetching, transcribing, choosing moments, cutting, captioning, reframing, grading, scoring and (optionally) publishing.

There are two ways to get it:

  • The hosted workshop at YOUR-DOMAIN.com — nothing to install. Your video is processed on our servers; see the privacy policy for exactly what that means. Accounts are created by an administrator.
  • Self-hosted — the same software on your own machine or server, where nothing ever leaves your hardware. See Self-hosting.

And two ways to drive it:

  • The Studio — the multi-user web app: accounts, a clip editor, channel monitoring and publishing.
  • The CLI — one command, good for batches and scripting. Self-hosted only.
Which sections apply to me? Everything about using the app — the Studio, the editor, Auto-Clip, publishing — is identical either way. The install, configuration, deployment and CLI sections only matter if you are running your own copy.
The mental model. A job takes one source and emits clips into output/<project>/, each with a matching .json sidecar holding its title, description, score and source timestamps. Everything else in the app is a way of shaping or re-shaping that job.

Using the hosted workshop

Nothing to install. Go to YOUR-DOMAIN.com, sign in, and skip straight to the Studio tab.

  • Getting an account — there is no public sign-up. An administrator creates each account and gives you the credentials. Ask for access.
  • What is stored — your uploads, transcripts, clips, settings and any channel tokens live on our servers. The privacy policy itemises all of it.
  • Fair use — renders are queued one at a time per instance, so be reasonable with batch sizes. Limits may apply to upload size and volume.
  • Keep your own copies. Download anything you care about. The service is a workshop, not a backup.
  • No CLI — the command line is for self-hosted installs only.

Everything from the Studio onwards applies to you. You can skip Requirements, Install and Deployment entirely.

Self-hosting

The hosted workshop and the self-hosted software are the same program. Running your own gets you three things: your footage never touches anyone else's disk, there are no limits but your hardware, and nobody can take it away from you.

On a laptop, for a look around:

pip install -r requirements.txt
python dashboard.py          # -> http://localhost:8000

On a server, on your own domain, with HTTPS:

cp .env.example .env         # set PUBLIC_BASE_URL + SUPERADMIN_CODE
docker compose up -d --build

Read Requirements and Install next, then Deployment when you want it public. DEPLOY.md in the repository is the full walkthrough.

Nothing phones home. There is no licence check, no telemetry and no analytics. A self-hosted instance never contacts us — outbound traffic only happens when you fetch a source URL, call a cloud LLM you keyed, or publish.

Requirements self-hosted

ThingNotes
Python 3.10+Required. 3.11+ recommended.
ffmpegA Windows build ships in .bin/. On macOS/Linux install it and put it on your PATH.
DiskSources are downloaded before clipping. A 1-hour 1080p video is roughly 1–2 GB.
GPUOptional. CUDA makes transcription several times faster; CPU works with smaller whisper models.
OllamaOptional, only for the local-LLM picker.
Google libsOptional, only for YouTube upload: google-api-python-client, google-auth-oauthlib.

Install

git clone <your-copy-of-arch-angel>
cd clipper
pip install -r requirements.txt

For one-click YouTube publishing, add the Google client libraries:

pip install google-api-python-client google-auth-oauthlib

Check ffmpeg is reachable:

ffmpeg -version

On Windows the bundled binary in .bin/ is used automatically if ffmpeg isn't on your PATH.

Quick start

From the command line

# five shorts from a URL
python main.py "https://youtube.com/watch?v=..." --clips 5

# from a local file
python main.py --file lecture.mp4 --clips 3

# local LLM picking, with music and meme SFX
python main.py URL --picker ollama --music --sfx

# just tell me which moments you'd pick — render nothing
python main.py URL --dry-run

Clips land in output/<project>/. Each .mp4 has a sibling .json with its metadata.

From the Studio

python dashboard.py       # -> http://localhost:8000

Open the URL and you'll land on the sign-in page.

First run & the super admin

The very first time you start the server there are no accounts, so the login page shows a Create super admin form instead. It asks for a username, a password, and the super-admin code.

  • The code comes from SUPERADMIN_CODE in your .env.
  • If you haven't set one, the default is ARCH-ANGEL-2026.
  • Change .env before you create the first account — the code is re-read from the environment right up until an account exists.
  • After setup, rotate it in Users → Super-Admin Code.
Rotate the default. Anyone who knows the code can promote themselves to super admin from the Settings tab. Treat it like a root password, not a convenience.

Accounts & roles

There is no open sign-up. A super admin creates every account.

RoleCan doCannot do
Super admin Everything: API keys and providers, the YouTube app credential, user management, clearing the error log.
User Render, edit, use the media library, connect their own YouTube channels, track channels, publish. Touch the API section, manage users, or see another user's channels.

Sessions are signed cookies (HttpOnly, SameSite=Lax, and Secure automatically over HTTPS) valid for 14 days, stored in config/sessions.json so a restart doesn't sign everyone out.

The Studio tab

Where jobs start. Paste a URL or drop a file, then tune:

  • Picker — which backend chooses the moments (see Pickers).
  • Ensemble — tick two or more pickers and their agreement ranks moments higher.
  • Whisper modeltinylarge-v3. The Fast/Balanced/Quality/Max preset chips just set this.
  • Clips — how many to keep.
  • Music — on/off, a specific track or auto mood-match, volume, and swells.
  • SFX — automatic meme stings.

Captions use whatever you designed in Subtitle Studio, and motion effects come from your Effects selection — both are global choices that apply to Studio and Enhance renders.

Progress streams live underneath, and the log is mirrored into Telemetry.

Enhance

Takes a clip you already have — dropped in or by URL — and re-runs only the finishing stages: captions, effects, music, reframe, trim. Use it when the cut is right but the dressing isn't. For fine control over a single clip, the clip editor is usually the better tool.

Effects

A gallery of motion and overlay effects grouped by category — zoom, motion, transition, glitch, colour, overlay. Selecting effects here stores them in your browser and applies them to Studio renders, Enhance runs and the clip editor. The count shows in the editor so you always know what's armed.

Subtitle studio

Design the caption look once; every render uses it.

ControlEffect
Animationpop, box, karaoke, bounce, minimal
Positiontop / middle / bottom
FontArial, Impact, Montserrat, Bebas Neue, Poppins
UppercaseForce caps on or off
Text colourMain caption colour
HighlightThe active/emphasised word

The preview panel reacts to all six live. Colours are converted to ASS subtitle format (&H00BBGGRR) when the render runs — you never have to think about that.

Media library

Upload your own background music (MP3, M4A, WAV, OGG) and sound-effect stings. Uploads appear immediately in every track picker — Studio, Enhance, the clip editor and per-channel profiles. You can play them in place, delete them, and drag the grip handle to reorder; the order is remembered in your browser.

Clips

Every finished short, newest first, with its score and file size. Hover a tile to preview it. From here you can open the editor or publish to YouTube, TikTok or Instagram.

The clip editor

Click any clip to open a non-destructive editor. Adjusting anything and pressing Re-render produces a new file; the original is left alone.

Timeline

  • A real audio waveform with a draggable trim region — grab an edge to move one end, or the middle to slide the whole selection.
  • Space plays and pauses. I and O set the in and out points to the playhead.
  • Loop plays just the trimmed region on repeat.
  • The numeric Start/End fields stay in sync both ways — they are what the render actually uses.

Live preview

The picture updates as you drag: brightness, contrast, saturation, gamma, sharpen and all preset looks are approximated with CSS filters; grain and vignette render as overlays; fades dim the frame near the trimmed edges; captions and the watermark show in position.

Preview is an approximation. The badge in the corner says so. ffmpeg is the source of truth — the preview exists to save you a render, not to replace one.

What you can change

GroupControls
TrimStart, end (seconds)
TransformReframe (crop 9:16 / blur pad), resolution, speed, fade in/out
LookPreset look, brightness, contrast, saturation, gamma, grain, sharpen, vignette
AudioBackground music on/off, track, volume, meme SFX
CaptionsOn/off (uses your Subtitle Studio design) plus armed effects
WatermarkText, position, opacity

Analytics

Computed live from your own output, not from any external service: clips per day for the last fortnight, a breakdown by source project, the score distribution, and cumulative library growth — plus totals for clip count, average score, average length and library size on disk.

Telemetry & the error log

Telemetry shows the live pipeline status and streaming log while a job runs.

Below it, the error log is persistent and always worth checking when something goes quiet. It collects:

  • Render failures, with the last lines of pipeline output;
  • YouTube connect and upload failures, with the exact provider error;
  • Unhandled server errors, with a traceback;
  • Browser-side errors and unhandled promise rejections.

Each entry expands to show detail. Super admins can clear the log.

Settings

  • Appearance — follow system, light or dark, plus an accent colour. The choice is shared with this website.
  • Account — change your password; enter the super-admin code to promote yourself.
  • API providers (super admin) — cloud LLM keys and models.
  • YouTube app credentials (super admin) — upload the Google OAuth client JSON and read the exact redirect URI to register.

Keyboard shortcuts

KeysDoes
Ctrl/ + KCommand palette — jump to any tab, run a command
G then SGo to Studio
G then CGo to Clips
G then AGo to Analytics
?Show the shortcut hint
SpacePlay / pause (clip editor)
I / OSet trim in / out (clip editor)
EscClose the top-most dialog
/Filter sections (this manual)

The Auto-Clip monitor

Give it a channel or playlist URL and a poll interval. From then on, every new upload is downloaded and clipped automatically using that channel's own settings. Tracked channels are written to config/monitor_channels.json and re-armed when the server restarts.

Jobs are serialised — a burst of uploads won't launch several ffmpeg pipelines at once.

Per-channel profiles

This is the part people miss. Each tracked channel stores its own render profile, so a podcast and a gaming channel can be treated completely differently without touching your global defaults.

SettingNotes
PickerDefault, heuristic, energy, Ollama or cloud LLM
Clips per video1–20
Subtitle styleAny of the five animation modes
LookVivid, warm, cool, cinematic, dramatic…
Min / max clip lengthSeconds. Leave blank to inherit the global default.
Music & SFXPer-channel on/off
Auto-publishPost new clips to YouTube automatically
Upload asShort, regular video, or auto
PrivacyPublic, unlisted or private

Anything left on Default inherits your global configuration. The tracked-channel list shows a one-line summary of each profile, e.g. picker: ollama · len: 15–60s · 3 clips · → YouTube (short, unlisted).

Channel preview

Each tracked channel has a Recent videos button. It opens a grid of that channel's latest uploads with thumbnails, durations and view counts, split into Videos and Shorts tabs.

Clip this on any of them renders it immediately using that channel's saved profile. This is a completely separate, on-demand path — your background auto-tracking keeps running untouched.

Listing uses yt-dlp, so it costs no API quota and works for any channel.

Connecting YouTube

Publishing uses the official YouTube Data API v3 with a browser redirect flow, so it works for remote and hosted users — not just whoever is sitting at the server.

One-time, by a super admin

  1. In the Google Cloud Console, create a project and enable YouTube Data API v3.
  2. Configure the OAuth consent screen. While it's in testing, add each person as a test user.
  3. Create an OAuth client of type Web application — not Desktop.
  4. Under Authorised redirect URIs, add exactly the URI shown in Settings → YouTube app credentials, for example https://YOUR-DOMAIN.com/api/youtube/oauth/callback.
  5. Download the client JSON and upload it in that same panel.

Then, per user

Channels → Connect a channel → sign in with Google. The channel appears in that user's private list. Publish here switches the active target. Connect as many as you run.

The redirect URI must match to the character. http vs https, localhost vs 127.0.0.1, and any trailing slash all count as different URIs. This is the single most common cause of a failed connect.

Shorts vs. regular video

When you publish a clip to YouTube, a dialog asks how to file it:

  • Short — adds the #Shorts hint so YouTube shelves it as a Short.
  • Regular video — publishes plainly, with no Shorts hint.
  • Auto — leaves the metadata as-is and lets YouTube decide.

The same dialog sets privacy (public / unlisted / private) and lets you override the title and description for that upload.

For tracked channels you choose all of this in advance, in the channel's profile, so automatic posts go out exactly how you want without anyone watching.

YouTube also classifies by aspect ratio and length, so a vertical clip under three minutes will generally land as a Short regardless. The #Shorts hint is the part we control.

TikTok & Instagram

Both are supported through their official content-posting APIs, which require an approved developer app and an access token. Paste your token in Publish → Connect and the platform lights up.

There is no way around this and no unofficial fallback — it's a platform rule. Everything up to the upload is unaffected.

CLI reference

python main.py [URL] [options]
FlagMeaning
--file PATHUse a local video instead of a URL
--clips NNumber of clips to produce
--picker NAMEheuristic · ollama · energy · api
--model NAMEWhisper model: tiny, base, small, medium, large-v3
--sub-mode MODEpop · box · karaoke · bounce · minimal
--sub-pos POSbottom · middle · top
--no-subsDisable captions entirely
--music / --no-musicForce background music on or off
--sfx / --no-sfxAutomatic meme sound effects on or off
--sfx-volume FSFX volume, 0.01.0
--watermark TEXTBurn in a text watermark (your handle)
--out DIROutput directory, overriding config
--keep-sourceKeep the source video after clipping (it is deleted by default)
--delete-sourceDelete the source after clipping — the default, listed for scripts that want it explicit
--config PATHUse a different config file (default config.json)
--dry-runPick moments and print them; render nothing
What "source" means here. Only the video ARCH-ANGEL fetched or you uploaded — the copy in downloads/ or output/_uploads/, plus the working copy under work/. A file you pointed it at with --file is never deleted, and neither is an existing clip you re-ran through Enhance. Losing your own footage is not a recoverable mistake, so the cleanup refuses to touch anything outside its own scratch directories. Nothing is removed if the render produced no clips.

Worked examples

# quick pass, small model, no music
python main.py URL --clips 4 --model base --no-music

# maximum quality, karaoke captions at the top
python main.py --file talk.mp4 --model large-v3 \
  --sub-mode karaoke --sub-pos top --clips 6

# branded output
python main.py URL --watermark "@deadangel" --sfx --sfx-volume 0.4

# see the moment list before committing to a render
python main.py URL --picker api --dry-run

Pickers explained

PickerHow it decidesCostBest for
heuristicTranscript structure — hooks, questions, sentence rhythm, keyword densityFreeFast, dependable default
energyAudio dynamics: laughter, emphasis, volume swingsFreeReaction content, comedy, streams
ollamaA local LLM reads the transcript and argues for momentsFree, needs OllamaNuanced picks with no bill
apiCloud LLMs — every provider you've keyed is queried in parallelTokensBest quality when it matters

Ensemble

In the Studio you can tick more than one. Moments that several pickers independently choose are ranked highest — agreement is a better signal than any single model's confidence. Per-channel profiles support this too.

Caption styles

ModeLooks like
popWords appear on a dark rounded pill — the default, reads anywhere
boxSolid highlight block behind the text, high contrast
karaokeOutlined text with the active word highlighted as it's spoken
bounceOutlined text with a springy per-word motion
minimalClean text and a soft shadow, nothing else

All five ride on real word-level whisper timings, in whatever language was spoken.

Look & grading

Preset looks — vivid, warm, cool, cinematic, dramatic, clarity, vintage, B&W — are starting points. On top you get manual brightness, contrast, saturation, gamma, grain, sharpen and vignette, plus speed and fades. The clip editor previews all of it live before you commit.

config.json

Holds pipeline defaults: transcription, picker, subtitles, resize, filters, music, effects and upload. Every field falls back to a sensible default in config.py, so the file can be partial or missing entirely. The Studio edits it visually — you rarely need to open it by hand.

Environment (.env)

Loaded automatically at startup. Real shell environment variables always win over the file.

VariablePurpose
APP_ENVdevelopment (default) or production. See Deployment.
PUBLIC_BASE_URLThe public origin users reach, e.g. https://YOUR-DOMAIN.com. Required in production.
SUPERADMIN_CODEBootstrap and elevation code. Default ARCH-ANGEL-2026 — change it.
SECRET_KEYSigns public clip links. Auto-generated into config/secret.key if unset.
CLIPPER_HOSTBind address. 127.0.0.1 in dev, 0.0.0.0 in production.
CLIPPER_PORTPort. Default 8000.
TRUST_PROXYBelieve X-Forwarded-*. On only when a reverse proxy is really in front.
ENABLE_HSTSSend Strict-Transport-Security. Leave off until HTTPS is confirmed.
ALLOW_SHUTDOWNPermit the in-app "kill server" button. Off in production.
OPEN_BROWSERAuto-open a browser at startup. Off in production.
MAX_BODY_BYTESLargest accepted upload. Default 2 GiB.
MEDIA_LINK_TTLLifetime of a signed public clip link, in seconds. Default 6 h.
OAUTH_REDIRECT_BASELegacy alias for PUBLIC_BASE_URL; still honoured.
OPENAI_API_KEYOptional cloud-LLM picker keys. Set any of them; every keyed provider is queried in parallel.
ANTHROPIC_API_KEY
GEMINI_API_KEY
GROQ_API_KEY
OPENROUTER_API_KEY
DEEPSEEK_API_KEY
MISTRAL_API_KEY
# .env — production
APP_ENV=production
PUBLIC_BASE_URL=https://YOUR-DOMAIN.com
SUPERADMIN_CODE=change-me-before-first-run
SECRET_KEY=<python -c "import secrets;print(secrets.token_hex(32))">
CLIPPER_HOST=127.0.0.1
CLIPPER_PORT=8000
TRUST_PROXY=1
ENABLE_HSTS=1
ALLOW_SHUTDOWN=0

Start from .env.example, which documents every option.

HTTP API

The Studio is a thin client over a plain JSON API. Every route below the public ones needs a session cookie; some need the super-admin role.

RouteDoes
POST /api/runStart a render job
GET /api/statusCurrent job status and streaming log
POST /api/cancelKill the running job and its children
GET /api/clipsList finished clips with metadata
GET /media?path=Serve a clip (supports HTTP Range)
POST /api/publish/youtubeUpload a clip — takes kind and privacy
GET /api/youtube/accountsThe caller's connected channels
GET /api/monitor/listTracked channels and their profiles
GET /api/monitor/videosA tracked channel's recent uploads
GET /api/errorsThe error log
POST /api/errors/clearClear it (super admin)
GET /api/providersProvider/key status (super admin)

Cross-origin POSTs are rejected, so drive the API from the same origin or a server-side client.

Project layout

main.py            CLI entry (parse args -> run_pipeline)
dashboard.py       Web server: routing, auth, job runner, all /api/*
auth.py            Accounts, roles, sessions, super-admin code
publish.py         YouTube web OAuth + per-user channels
monitor.py         Auto-Clip channel monitor
downloader.py transcriber.py ai_picker.py renderer.py    pipeline stages
subtitles.py effects.py motion_fx.py filters.py music.py sfx.py
config.py config.json    typed config + defaults
web/               landing · docs · privacy · terms · credits · login · studio
web/assets/        site.css · site.js · studio.js
assets/            bundled music + SFX
secrets/           OAuth app + per-user tokens (git-ignored)
config/            auth · sessions · monitor_channels · yt_accounts
output/            your clips

Deployment

There is a full walkthrough in DEPLOY.md, plus ready-made Dockerfile, docker-compose.yml, a Caddyfile, an nginx config and a systemd unit under deploy/. The short version:

cp .env.example .env      # set APP_ENV, PUBLIC_BASE_URL, SUPERADMIN_CODE
docker compose up -d --build

Production mode

Setting APP_ENV=production flips the app out of laptop mode:

developmentproduction
Browser auto-openyesno
Remote "kill server"alloweddisabled, button hidden
X-Forwarded-* trustednoyes, via TRUST_PROXY
HSTSoffon
Public links built fromthe Host headerPUBLIC_BASE_URL only
Misconfigurationsilentprinted and logged at boot
PUBLIC_BASE_URL is required in production. It is used for the YouTube OAuth redirect and for the signed clip links handed to TikTok/Instagram. Host headers are attacker-controlled, so in production they are ignored in favour of this one configured value.

Essentials

  • Terminate TLS at a reverse proxy (Caddy does certificates automatically); keep the app on 127.0.0.1 or a Docker-internal network.
  • Turn on TRUST_PROXY=1 — but only when a proxy really is in front.
  • Change SUPERADMIN_CODE before the first boot, then rotate it in-app.
  • Set SECRET_KEY explicitly if you run more than one instance.
  • Point an uptime monitor at /healthz — it's public and cheap.
  • Rendering is CPU/GPU heavy and jobs are serialised, so size the box for one render at a time and run separate instances for more throughput.
  • Back up config/ and secrets/. Losing them means everyone signs in again and reconnects their channels.

How social platforms fetch a clip

TikTok and Instagram download the video from a URL their servers can reach, so it can't sit behind the session cookie. Rather than exposing output/, the app mints a short-lived HMAC-signed link for the single file being posted — bound to that exact path and expiry, verified server-side, and dead after MEDIA_LINK_TTL (six hours by default). Everything else stays authenticated.

Signals & restarts

The server handles SIGTERM (and SIGINT/SIGBREAK), cancelling any running job and draining in-flight requests, so docker compose restart and systemctl restart are safe.

The web Studio loads a few front-end libraries from public CDNs (Chart.js, Sortable, NProgress, tippy.js, WaveSurfer). Each has a graceful fallback, so a blocked CDN degrades rather than breaks. To run fully air-gapped, vendor them into web/assets/vendor/ and update the tags.

Security

  • Passwords and the super-admin code are salted and hashed with PBKDF2-HMAC-SHA256 (200,000 iterations). Neither is ever stored or returned in clear text.
  • Sessions are opaque random tokens in HttpOnly cookies.
  • Login attempts are rate-limited per address.
  • Cross-origin POSTs are blocked.
  • API routes are role-gated server-side, not just hidden in the UI.
  • Media serving is path-traversal-checked and confined to output/.
  • YouTube OAuth uses PKCE, and refresh failures flag the channel for reconnection rather than failing silently.

If you find a security problem, please report it privately before disclosing it.

Your data

On the hosted workshop, the files below live on our servers and the privacy policy governs them. On a self-hosted install they are on your disk and nobody else can reach them. Either way, the layout is the same:

PathHolds
config/auth.jsonUsers (hashed passwords), hashed super-admin code
config/sessions.jsonActive sessions
config/yt_accounts.jsonPer-user channel manifest
secrets/youtube/<user>/Per-user OAuth tokens
secrets/client_secret.jsonThe Google OAuth app credential
config/monitor_channels.jsonTracked channels and profiles
output/Your clips and their metadata

See the Privacy Policy for the full picture.

Troubleshooting

"Missing code verifier" when connecting YouTube

A PKCE mismatch: the verifier generated when the consent URL was built has to be replayed at the token exchange. This is handled — if you still see it, you're running an older build. Restart the server and retry.

"invalid_grant" or "redirect_uri_mismatch"

Almost always the redirect URI. Copy it verbatim from Settings → YouTube app credentials into the Google Cloud console. Also confirm the OAuth client type is Web application. Authorisation codes are single-use and expire quickly, so don't reload the callback URL.

The super-admin code isn't accepted

Edit .env before creating the first account, then restart the server. Once an account exists, the code is fixed in config/auth.json and must be rotated in-app instead.

Transcription is extremely slow

You're on CPU. Use --model tiny or base, or install a CUDA-enabled build. The Settings tab reports which engine was detected.

ffmpeg not found

Install it and put it on your PATH. On Windows, confirm .bin/ffmpeg.exe is present.

A render failed and I don't know why

Telemetry → Error log. Render failures are recorded there with the last lines of pipeline output.

Nothing happens when I track a channel

Monitors poll on an interval — with a 300-second interval, nothing happens for up to five minutes, and only for uploads that appear after you started tracking. To clip something that already exists, use Recent videos → Clip this.

The port is already in use

Set CLIPPER_PORT to something else, or stop the other instance.

Licence & notices

ARCH‑ANGEL is built by dead-angel. It stands on excellent open-source work, each under its own licence:

ProjectUsed for
yt-dlpFetching source video
FFmpegAll decoding, filtering, encoding
faster-whisper / CTranslate2Transcription
OllamaOptional local LLM picking
Google API Python ClientYouTube Data API v3
Chart.js · WaveSurfer.js · Sortable.js · NProgress · tippy.jsStudio interface
Fraunces · Newsreader · Courier Prime · CaveatTypography on this site

You are responsible for having the right to clip and republish whatever you feed it. See the Terms of Service.

Credit

All credit for ARCH‑ANGEL goes to angel. The credits page says it properly.

thanks for reading the whole thing.


Something missing or wrong here? It probably belongs in this manual — say so.