mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-24 18:18:17 -06:00
M15.2: merge main (disk cache, 3D Camera Tracker); camera analysis in workers; no disk cache on wasm32
The 3D Camera Tracker analysis joins the offloaded jobs. The disk cache stays off in the browser (std::env::temp_dir panics on wasm32 once a config store exists). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -147,6 +147,34 @@ effectcraft-cli exec warp.analyze '{"layer":"#1","wait":true}' shaky.ecproj --sa
|
||||
effectcraft-cli exec warp.status '{"layer":"#1"}' shaky.ecproj --json
|
||||
```
|
||||
|
||||
### 3D Camera Tracker
|
||||
|
||||
Animation ▸ Track Camera and the Tracker panel's button (`track.camera {layer?, shotType?:
|
||||
fixed|variable|specify, aov?, solveMethod?: auto|typical|flat|tripod, detailed?, wait?}`) apply
|
||||
Effect ▸ Perspective ▸ 3D Camera Tracker (or reuse the layer's) and analyse it in the background:
|
||||
"Analyzing in background (step 1 of 2)" tracks features, "Solving camera" solves.
|
||||
`camera.analyze {layer?, effect?, wait?}` re-runs it, `camera.cancel` stops it without writing
|
||||
anything. `camera.solveStatus` reports `progress`/`banner`, `analyzed`, `solved`, `methodUsed`,
|
||||
`averageError` (pixels), `focalLength`, `horizontalAngleOfView`, the current frame's `camera`
|
||||
(position, orientation, zoom) and `groundPlane`. `camera.points {time?}` lists the solved points
|
||||
visible now with their `id`, `comp` position, `depth`, `world` position and `error`.
|
||||
|
||||
Select points with `camera.selectPoints {points, add?, toggle?}` (the viewer's click / Shift-click /
|
||||
marquee), then the right-click menu's commands: `camera.setGroundPlane {points?}` (Set Ground Plane
|
||||
and Origin), `camera.createFromSolve {kind: text|solid|null|shadowCatcher, points? | target:
|
||||
{center, normal, size?}, multiple?}` (Create Text / Solid / Null / Shadow Catcher, Camera and Light,
|
||||
Create Multiple …), `camera.deletePoints {points?, wait?}` (re-solves; with Auto-delete Points
|
||||
Across Time the same feature's other tracks go too) and `camera.create` (the Create Camera button).
|
||||
Each is one undo step; the first create adds the one-node "3D Tracker Camera" keyed on every frame.
|
||||
Changing the layer's frames clears the analysis; changing `effects/#1/shotType`,
|
||||
`effects/#1/horizontalAngleOfView`, `effects/#1/advanced/solveMethod` re-solves the stored tracks.
|
||||
|
||||
```sh
|
||||
effectcraft-cli run shot.ecproj track.camera '{"layer":"#1","wait":true}' \
|
||||
camera.points '{}' --json
|
||||
effectcraft-cli run shot.ecproj camera.createFromSolve '{"kind":"solid","points":[12,40,77]}' --save
|
||||
```
|
||||
|
||||
### Roto Brush & Refine Edge
|
||||
|
||||
The Roto Brush tool (Alt+W cycles Roto Brush / Refine Edge) paints in the Layer panel; agents use
|
||||
|
||||
+30
-2
@@ -16,18 +16,22 @@ cpal or muda. Everything in L0 to L4, the egui UI and the web app also build for
|
||||
| L0 | `time` | `Tick` (254 016 000 000 per second), rational frame rates incl. NTSC, SMPTE and drop-frame timecode |
|
||||
| L0 | `geom` | Vectors, matrices, quaternions, the layer transform (anchor, position, scale, orientation, rotation) |
|
||||
| L0 | `color` | sRGB and linear, HSL/HSV, luminance, the 38 blend modes, label colors |
|
||||
| L0 | `vp9enc` | VP9 intra-frame encoder (profile 0, 8-bit 4:2:0; lossless at quality 100) for WebM export, from the VP9 bitstream specification |
|
||||
| L0 | `opusenc` | Opus (CELT) encoder: RFC 6716 CELT-only fullband 48 kHz 20 ms packets, mono/stereo, and the RFC 7845 `OpusHead`, for WebM export audio |
|
||||
| L1 | `raster` | Premultiplied float images, sampling, affine and projective warps, blurs, compositing (parallel with rayon) |
|
||||
| L1 | `keyframe` | Animated values, keyframes with temporal ease and spatial Bezier, roving, hold, velocity |
|
||||
| L1 | `psd` | Photoshop PSD/PSB reader (layers, groups, masks, blend modes, text, layer effects, adjustment layers; 8/16/32-bit; RGB/CMYK/Gray/Lab) and a minimal writer, from Adobe's published format specification |
|
||||
| L1 | `path` | Bezier paths, path operators (trim, offset, round corners, zig zag, twist, merge…), stroking, coverage masks |
|
||||
| L2 | `project` | The document: items, compositions, layers, the property tree, render queue model, `.ecproj` serde |
|
||||
| L2 | `text` | Fonts, shaping, layout, per-glyph geometry, text animators and selectors |
|
||||
| L2 | `effects` | The effect registry (241 effects) and their CPU implementations |
|
||||
| L2 | `svg` | SVG import (W3C SVG 1.1/2 static subset: shapes, paths, transforms, `use`, CSS, gradients) and rasterisation at any scale |
|
||||
| L2 | `model` | 3D models for Advanced 3D: glTF 2.0 (`.gltf`/`.glb`) and OBJ/MTL import (meshes, PBR metallic-roughness materials and textures, node hierarchy, skins, animations), parametric primitives, extruded/bevelled outline meshes and polygon triangulation |
|
||||
| L2 | `track` | Motion tracking: feature/search region point tracking (pyramid normalized cross-correlation, Lucas–Kanade sub-pixel refinement), confidence, homography/affine/similarity solves |
|
||||
| L3 | `render` | Evaluation and compositing: sources, masks, effects, transforms, 3D, motion blur, mattes, blending, layer cache, audio mixdown |
|
||||
| L3 | `media` | Footage decoding (FilmCraft's pure-Rust codecs), image sequences, frame cache |
|
||||
| L3 | `media` | Footage decoding (FilmCraft's pure-Rust codecs), image sequences, Photoshop and SVG stills, frame cache |
|
||||
| L3 | `expr` | The expression engine (JavaScript via boa) with the After Effects object model |
|
||||
| L3 | `export` | Render queue encoding: H.264, ProRes, PNG/JPEG/TIFF/EXR sequences, GIF, audio |
|
||||
| L3 | `export` | Render queue encoding: H.264, ProRes, WebM (VP9 + alpha, Opus), PNG/JPEG/TIFF/EXR sequences, GIF, WAV/AIFF |
|
||||
| L3 | `gpu` | The GPU compositor and GPU effects on wgpu compute shaders (Metal, Vulkan, Direct3D 12, WebGPU), checked against the CPU renderer |
|
||||
| L3 | `lottie` | Lottie JSON / dotLottie import and export (layers, precomps, eased and spatial keyframes, shapes, masks, mattes) with a warnings list for what Lottie cannot express |
|
||||
| L4 | `engine` | `Session`: project, undo history, editor state, the command registry and menus |
|
||||
@@ -106,6 +110,14 @@ its evaluated inputs, excluding the transform. Static and transform-only layers
|
||||
editing one layer re-renders only that layer. Effects that read the clock directly are declared in
|
||||
`effects::TIME_DEPENDENT`, and a test checks every registered effect against that list.
|
||||
|
||||
A persistent **disk cache** (`render::disk_cache`, Settings ▸ Media & Disk Cache) backs both
|
||||
the layer cache and the viewer's RAM preview: layer buffers that were slow to render and every
|
||||
preview frame are written (LZ4, atomic rename, checksummed) under 128-bit content keys — the
|
||||
comp's content hash (the comp, what it uses, footage file size and time) plus frame, scale and
|
||||
view for frames; the layer key salted with the footage fingerprint for layers — so entries
|
||||
survive restarts and never go stale. LRU eviction keeps it under the size limit; Edit ▸ Purge
|
||||
empties it, `cache.diskStats` reports it, and the timeline draws disk-only frames in blue.
|
||||
|
||||
**Time effects** (Echo, Posterize Time, Timewarp…) read the layer at other times through
|
||||
`EffectHost::self_at`: the renderer renders the layer's source and masks (plus, optionally, the
|
||||
effects before it) at that layer time via `Renderer::layer_input`, cached under separate *input*
|
||||
@@ -208,6 +220,22 @@ auto-scale; cached per analysis and settings) and warps each frame; Synthesize E
|
||||
borders from neighbouring frames read with `EffectHost::self_at`. The effect lives in the
|
||||
effects crate, hence the `effects → track` edge.
|
||||
|
||||
**3D Camera Tracker** (`effects::camera_tracker`, `engine::camera_track`, `effectcraft-track`'s
|
||||
`camtrack`): `camera.analyze` / `track.camera` render the layer's input to the effect on a
|
||||
background thread and run structure from motion in two steps. Step 1 follows Shi–Tomasi features
|
||||
through the clip with pyramidal Lucas–Kanade (forward–backward checked, re-detected where the frame
|
||||
has none) into long 2D tracks. Step 2 solves the camera: parallax keyframes; a two-view start from
|
||||
the essential matrix (normalised 8-point in RANSAC) or a planar homography decomposition; incremental
|
||||
resection and triangulation; a sparse Levenberg–Marquardt bundle adjustment (Schur complement over
|
||||
the points, Huber loss) with the focal length fixed, shared, or per frame; a log-spaced and
|
||||
golden-section focal search for Fixed Angle of View / Variable Zoom; a rotation-only model for tripod
|
||||
pans, chosen by Auto Detect when it explains the tracks as well. Tracks and solve are stored as JSON
|
||||
in hidden effect parameters with keys: changing the layer's frames clears both, changing Shot Type,
|
||||
Angle of View, Solve Method or deleting points only re-solves. The solve's canonical frame maps to
|
||||
comp space so the first frame's camera is the default comp camera (or so a chosen ground plane is
|
||||
the X-Z plane at the origin); `camera.createFromSolve` keys a one-node "3D Tracker Camera" on every
|
||||
frame and places text, solids, nulls or a shadow catcher and light on the target plane.
|
||||
|
||||
**Roto Brush & Refine Edge** (`effectcraft_track::roto`, `effects::roto`, `engine::roto`):
|
||||
strokes (foreground, background, Refine Edge) are stored as JSON in the effect's hidden Strokes
|
||||
parameter with the base frame and segmentation span. Each frame is segmented by graph cut
|
||||
|
||||
+45
-18
@@ -1,9 +1,36 @@
|
||||
# Parity with After Effects
|
||||
|
||||
How close EffectCraft is to After Effects 2026, feature by feature, and how much work is left.
|
||||
Audited against the code at commit `5273f1b` (2 October 2026). Every feature in our catalog was
|
||||
graded from the code itself: a disabled menu entry, or a setting that is stored but never
|
||||
rendered, does not count as done.
|
||||
|
||||
## Current status (audit at commit `fa26ad9`, 2 October 2026, late)
|
||||
|
||||
| Measure | Value |
|
||||
|---|---|
|
||||
| **Feature parity, weighted by tier** (P0 ×3, P1 ×2, P2 ×1) | **≈ 93%** |
|
||||
| Unweighted | ≈ 91% |
|
||||
| P0 / P1 / P2 | ≈ 96% / 90% / 65% |
|
||||
| Features done / partial / missing | 41 / 50 / 1 of 92 (missing: a plug-in API) |
|
||||
| **Effects** | **298 of 298** After Effects 2026 effects exist (some still simplified) |
|
||||
| Disabled menu entries left | 26 commands (31 menu items) |
|
||||
| Remaining work | ≈ 50–55 agent-hours at the pace measured so far (≈ 150 by the original conservative audit scale) |
|
||||
| **Wall-clock estimate** | **≈ 11–14 hours** with five agents in parallel; ≈ 6–8 hours for 100% of P0 + P1 |
|
||||
|
||||
By area: Layers 98%, Output 95%, Compositions 95%, Automation 96%, Paint 95%, Text 95%, Import 96%,
|
||||
Animation 94%, Masks 94%, Preview 94%, Interface 93%, Shapes 93%, 3D 91%, Audio 90%, Project 90%,
|
||||
Tracking 88%, Effects 85%, Web 65%.
|
||||
|
||||
What is left, in priority order: the web app's depth (threads, storage, audio, non-blocking
|
||||
renders); stroke taper/wave and multi-segment dashes; variable mask feather points; real
|
||||
`sampleImage` and `footage()` in expressions; camera iris/bokeh and focus-link commands; GPU
|
||||
rendering of 3D runs and adjustment layers; Render Queue field render, crop/resize and templates;
|
||||
approximated effects (Subspace Warp, Key Cleaner) and Liquify's viewer brush; Advanced 3D motion blur
|
||||
and blend modes; a few viewer and colour-management menu items; OpenType features and variable
|
||||
font axes; AI/EPS/PDF import; more codecs; 59 preferences not yet wired; proxies; ScriptUI;
|
||||
Lumetri Scopes, Footage, Media Browser and Metadata panels; Content-Aware Fill; face tracking; and
|
||||
the "better than After Effects" items (a plug-in API, branching history, GPU particles).
|
||||
|
||||
The sections below are the original audit (morning of 2 October, ≈ 64%) and its updates, kept for
|
||||
history.
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -15,7 +42,7 @@ rendered, does not count as done.
|
||||
| P1 (professional daily use) | ≈ 38% |
|
||||
| P2 (long tail) | ≈ 17% |
|
||||
| Features done / partial / missing | 18 / 60 / 14 of 92 |
|
||||
| **Effects** | **297 of 298** After Effects 2026 effects by name (99.7%, after M9.11 and Warp Stabilizer; was 257); only the 3D Camera Tracker is left |
|
||||
| **Effects** | **298 of 298** After Effects 2026 effects by name (100%, after M9.11, Warp Stabilizer and the 3D Camera Tracker; was 257) |
|
||||
| Remaining work | ≈ **167 agent-hours** (139 h of features + ≈ 20% for 1:1 polish against After Effects) |
|
||||
| Wall-clock estimate | ≈ **42–50 hours** with five agents working in parallel and one integrating; ≈ 31–35 h for P0 + P1 only |
|
||||
|
||||
@@ -73,39 +100,39 @@ library groundwork for mask tracking and Warp Stabilizer.
|
||||
| Remaining (audit-scale agent-hours) | ≈ 167 | ≈ 100 |
|
||||
| Wall clock, five parallel agents | 42–50 h | **≈ 8–10 h at today's measured pace; ≈ 25 h by the audit's conservative figures** |
|
||||
|
||||
What is left is concentrated in large systems: the 3D camera tracker,
|
||||
the Warp Stabilizer and mask-tracking UI on top of
|
||||
the new tracking library, SVG/PSD import, WebM, a disk cache, and wasm threads. (The missing
|
||||
effect categories, 3D Channel, Immersive Video and OCIO, landed in M9.11; the scripting object
|
||||
model in M14.4; Roto Brush and Refine Edge in M6.7; native macOS menus, the Composition Flowchart,
|
||||
Timeline column/search depth, the Home screen and every After Effects workspace in the UI-polish wave.)
|
||||
What is left is concentrated in large systems: the Warp Stabilizer and mask-tracking UI on top of
|
||||
the new tracking library, and wasm threads. (The missing effect categories, 3D Channel, Immersive
|
||||
Video and OCIO, landed in M9.11; the scripting object model in M14.4; Roto Brush and Refine Edge in
|
||||
M6.7; native macOS menus, the Composition Flowchart, Timeline column/search depth, the Home screen
|
||||
and every After Effects workspace in the UI-polish wave; PSD/SVG import, WebM/WAV/AIFF output and
|
||||
the disk cache in the formats wave; the 3D Camera Tracker in M12.6.)
|
||||
|
||||
## By area
|
||||
|
||||
| Area | Weighted parity | Remaining (agent-hours) | Biggest gaps |
|
||||
|---|---|---|---|
|
||||
| Layers | 88% | 4.8 | frame blending, collapse transformations, slip edit |
|
||||
| Output | 85% | 4.2 | multiple output modules, pre-render, WebM, audio-only |
|
||||
| Output | 90% | 3.0 | multiple output modules, pre-render (WebM with VP9 alpha + Opus and WAV/AIFF audio-only landed: VP9 is intra-only, Opus CELT-only) |
|
||||
| Audio | 85% | 0.5 | audio to keyframes |
|
||||
| Import | 78% | 4.5 | PSD, SVG as shapes, Lottie |
|
||||
| Import | 88% | 2.0 | AI/EPS/PDF vector footage, PSD smart objects and 3D layers (PSD as footage/composition/retain layer sizes, SVG footage and Create Shapes from Vector Layer landed) |
|
||||
| Automation | ≈ 90% | 0.8 | scripting covers the documented core object model (AUT-2, M14.4: `app`, project items, comps, layers, properties and keyframes, text documents, markers, render queue, Script Console, `effectcraft-cli script`, MCP `run_script`); still missing: ScriptUI panels/dialogs, `.jsxbin`, sockets |
|
||||
| Shapes | 68% | 3.9 | taper and wave strokes (pen tool for shape paths and vertex editing landed in M6.5) |
|
||||
| Compositions | 72% | 6.0 | Essential Graphics panel (the marker dialog and Composition Flowchart landed) |
|
||||
| Animation | 65% | 10.4 | puppet, Wiggler/Smoother/Motion Sketch (motion-path handles and the graph editor transform box landed in M5.8) |
|
||||
| Text | ≈ 86% | 1.3 | vertical type is basic (upright characters, no tate-chu-yoko), no OpenType feature panel, no extruded strokes (extruded, bevelled text landed in M7.6); per-character styles, paragraph settings, on-canvas editing and the `sourceText` style API landed (M9.9–M9.10) |
|
||||
| Web | 85% | 1.0 | viewer frames and Roto Brush propagation still on the page's thread; GPU effects in the browser (browser storage, Web Audio, Web Worker renders/analyses, WebGPU viewer and offline install landed in M15.2) |
|
||||
| 3D | 72% | 12.0 | 3D camera tracker, multi-view layouts, Advanced 3D motion blur and blend modes, cameras/lights from models; Advanced 3D (glTF/OBJ models, primitives, extruded text and shapes, PBR, image-based light, shadow maps, GPU rasteriser) landed in M7.4–M7.6 |
|
||||
| Effects | ≈ 75% | 5.0 | GPU versions of more effects and the 3D Camera Tracker (every other After Effects effect exists since M9.11 and M12.5) |
|
||||
| 3D | 76% | 10.0 | multi-view layouts, Advanced 3D motion blur and blend modes, cameras/lights from models; Advanced 3D (glTF/OBJ models, primitives, extruded text and shapes, PBR, image-based light, shadow maps, GPU rasteriser) landed in M7.4–M7.6 |
|
||||
| Effects | ≈ 75% | 5.0 | GPU versions of more effects (every After Effects effect exists since M9.11, M12.5 and M12.6) |
|
||||
| Interface | 70% | 6.0 | Timeline horizontal scrolling of many columns, a richer Learn area (native macOS menu bar, Timeline columns/search/reveal-add, Home screen with recent projects and all AE workspaces landed; viewer rulers/snapping/channels/snapshots landed in M0.13) |
|
||||
| Project | 53% | 8.1 | 8/16/32-bit pipeline and colour management, auto-save, proxies, folder moves |
|
||||
| Masks & roto | 68% | 6.0 | variable-width mask feather points, Roto Brush's learned (3.0) segmentation model (mask tracking and Mask Interpolation landed in M6.6; Roto Brush & Refine Edge with graph-cut segmentation, flow propagation, edge matting, decontamination and Freeze in M6.7) |
|
||||
| Preview | 55% | 6.1 | GPU 3D and adjustment layers, disk cache (region of interest, snapshots, exposure and Fast Previews landed in M0.13) |
|
||||
| Tracking | ≈ 70% | 4.0 | 3D camera tracker, face tracking and Subspace Warp's mesh warp (Rolling Shutter Repair landed in M9.11; point tracker, mask tracking and Warp Stabilizer landed in M6.x / M12.5) |
|
||||
| Preview | 62% | 5.0 | GPU 3D and adjustment layers (persistent disk cache with the blue cache bar landed; region of interest, snapshots, exposure and Fast Previews landed in M0.13) |
|
||||
| Tracking | ≈ 85% | 2.5 | face tracking, Subspace Warp's mesh warp, lens distortion in the camera solve (Rolling Shutter Repair landed in M9.11; point tracker, mask tracking, Warp Stabilizer and the 3D Camera Tracker landed in M6.x / M12.5 / M12.6) |
|
||||
| Paint | 0% | 4.0 | Brush, Clone Stamp, Eraser |
|
||||
|
||||
## Effects still missing
|
||||
|
||||
The 3D Camera Tracker (built on the tracking library by other work). Boris FX
|
||||
None: the 3D Camera Tracker landed in M12.6. Boris FX
|
||||
Mocha and Cineware are third-party and not counted.
|
||||
|
||||
### Added in M9.11 (39 effects)
|
||||
@@ -138,7 +165,7 @@ Mocha and Cineware are third-party and not counted.
|
||||
10. ~~Drag-to-dock and floating panels, saved workspaces, native macOS menus.~~
|
||||
11. Auto-save, crash recovery, recent projects (in progress).
|
||||
12. Expression gaps: `sampleImage`, `footage()` (the `sourceText` style API landed in M9.9).
|
||||
13. Lottie (in progress), WebM, SVG and PSD import.
|
||||
13. ~~Lottie, WebM, SVG and PSD import~~ (landed; a disk cache too).
|
||||
14. Puppet and paint tools (in progress).
|
||||
15. Preferences and a shortcut editor that can rebind (in progress); real Wiggler, Smoother and Motion Sketch; the marker dialog.
|
||||
|
||||
|
||||
+4
-3
@@ -50,7 +50,7 @@ thread), `?nosw` (don't register the service worker).
|
||||
|---|---|
|
||||
| frame render thread pool (`ui-egui/src/frames.rs`) | `Frames::pump`: queued frames render on the UI thread after each egui frame, most important first (the viewer's frame, then prefetch), within 40 ms (24 ms while playing) |
|
||||
| Render Queue on a background thread | a Web Worker running a second engine instance (below); progress streams back, the page never waits |
|
||||
| tracker / mask tracker / Warp Stabilizer / Roto Brush Freeze threads | the same workers; the analysed property group comes back as one undo step |
|
||||
| tracker / mask tracker / Warp Stabilizer / 3D Camera Tracker / Roto Brush Freeze threads | the same workers; the analysed property group comes back as one undo step |
|
||||
| `rayon` parallel loops (raster, effects, export batches) | rayon's global pool falls back to the calling thread when threads are unavailable: same code, serial |
|
||||
| `std::time::Instant` / `SystemTime` (panic on wasm32) | `web-time` (re-exports `std::time` on native) |
|
||||
| `std::fs` project reads/writes (`FsServices`) | `files::WebServices`: the virtual file table, persisted (below); saving also downloads the `.ecproj` |
|
||||
@@ -97,8 +97,8 @@ of the same module (the page compiles it once and posts the `WebAssembly.Module`
|
||||
compiled twice) and its own memory (`crates/engine/src/offload.rs`, `src/worker.rs`):
|
||||
|
||||
1. The session's `Offload` serializes a `WorkerRequest`: the project JSON, the footage paths it
|
||||
reads, and the job (`render` with the resolved queue items and output paths; `warp`, `track`,
|
||||
`maskTrack`, `rotoFreeze` with the target and analysis parameters).
|
||||
reads, and the job (`render` with the resolved queue items and output paths; `warp`, `camera`,
|
||||
`track`, `maskTrack`, `rotoFreeze` with the target and analysis parameters).
|
||||
2. `js/host.js` takes an idle worker from its pool (or starts one), sends it the footage bytes it
|
||||
doesn't have yet, then the request.
|
||||
3. The worker runs the job on a plain engine session, blocking, and posts `WorkerReply`s: render
|
||||
@@ -203,6 +203,7 @@ every request and reply, a render through an in-process offload).
|
||||
frame at a time between UI frames).
|
||||
- GPU effects run only in the desktop GPU path: in the browser, effects run on the CPU (inside
|
||||
workers for renders) because the page's thread can't wait for mid-render readbacks.
|
||||
- No disk cache (Settings ▸ Media & Disk Cache) in the browser: the layer cache stays in memory.
|
||||
- Cancelling an analysis drops its partial result (the worker is terminated).
|
||||
- Stored media count against the origin's storage quota; `effectcraft.listStored()` shows usage,
|
||||
`removeStored` frees it (there is no storage manager in the UI yet).
|
||||
|
||||
Reference in New Issue
Block a user