Commit Graph
3365 Commits
Author SHA1 Message Date
arnav-hiwarkar 75b75f8463 fix(properties): reveal transform rows on double-click (#1456) 2026-10-15 01:25:46 +05:30
Davit MatchakhelidzeandClaude Opus 5.5 fb51427ec0 i18n: Georgian (ka) interface language
Adds Georgian (ქართული, code ka) to Settings > General > Language, with
Match System for ka / ka-GE / ka_GE.UTF-8 locales:

- i18n.rs: the menu catalog, the same 628 (command, English label) keys as
  the other languages, plus the Undo/Redo entries, the workspace and 3D
  view shortcut submenus, the Reveal labels on Windows and Linux and the
  macOS Services items.
- i18n/ui.rs: GEORGIAN, every row of the Chinese panel catalogs (687), and
  GEORGIAN_PANELS, 195 rows for text the panels send through tr() that no
  catalog has yet (form dialogs, drop messages, screen-reader names,
  context menus, Project and Timeline column headers). The tests hold
  them to the same rules (unique, same placeholders, used by a converted
  panel) and keep them from shadowing a Chinese row; prefs_live.rs joins
  the converted files.
- i18n/effects.rs: Georgian names for all 307 effects and 21 categories;
  the CC series and brand names stay in English.
- Properties > Text Animation: the Add Animator and Presets buttons grow
  with their translated labels (English keeps 116 / 84 px).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-14 22:50:28 +04:00
Davit Matchakhelidze 5539bb4a95 web: Match System follows ?lang= and the browser's languages
Match System (the default) stayed English in the browser build, which has
no system-locale probe (#229). The page now passes ?lang= from the address,
then navigator.languages, and Match System takes the first of them with a
catalog (an English tag counts), else English. A language chosen in
Settings still wins; native builds are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
(cherry picked from commit 73c064e3858068ed19fbfafbdd0b3c8cc229a42e)
2026-10-14 22:50:03 +04:00
Davit Matchakhelidze a1a1a36a41 i18n: context menus, tab titles, workspaces and column headers reach the catalogs
Text drawn outside the menu bar stayed English in every language:

- i18n::command_label: the Timeline / Composition layer and property
  context menus now take the menu bar's row for the same command (… or
  ...), else the panel catalog's row; the layer menu's Effects submenu
  draws effect and category names through the effect catalog.
- The dock's tab titles (and the dragged tab), the Composition, Effect
  Controls and Properties tabs, and the workspace bar and menus use the
  window.panel and window.workspace rows every menu catalog already has.
- The Project panel's search hint, details hint and column headers, and
  the Timeline's column headers and Columns menu go through tr().

The Simplified and Traditional Chinese rows for "Type" read 文字 (text, the
Type tool); the string is the Camera Settings field and now the Project
column, so they read 类型 / 類型. dock_ui.rs joins the converted files. Languages without a row for a
string keep the English, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
(cherry picked from commit b335f1e2764929b78d7b678ec026f680b1a8129f)
2026-10-14 22:50:03 +04:00
dotCooCoo 2f437ea551 EPS and PDF: huge arcs, long paths and loop passes count against the import's limits
The EPS interpreter counted one operation against its budget for each
name it ran, whatever the operator did, and paths had no length limit.
Parsing one arc at a radius of 10^300 passed 3 GiB of resident memory
within 43 seconds. A loop of arc at a radius of 2.2 x 10^18 was still
running after 60 seconds, and a loop of pathbbox on a path of 100,001
elements and { {} loop } loop did not finish within 20 seconds. PDF
content had no path limit either: parsing a page that repeats
0 0 1 1 re 4,000,000 times peaked at 2.3 GiB of resident memory.

A path now holds at most 1,048,576 elements (MAX_PATH, the limit of
VectorCraft's EPS interpreter). In EPS, moveto, lineto, curveto,
closepath, arc, charpath and the rect operators check it, and an
operator that would take a path past it stops the program with
"PostScript (path limit)". In PDF, m, l, c, v, y, h and re (each adds
at most five elements) stop the content stream with
"content (path limit)" when the path already holds more than 1,048,571.
Each path element an operator makes, reads or copies (arc, rectangle
arrays, charpath, pathbbox, clip, eoclip, gsave, save) now counts
against the EPS budget. Each pass of repeat, for, loop and forall
counts too, and so does each procedure item that is not a name. A
moveto right after a moveto replaces it, as the PostScript Language
Reference describes, so text placed with moveto and show leaves one
element.

Five tests in tests_import.rs cover the loops, the path limit in both
interpreters and runs of moveto and show.
2026-10-14 10:19:05 -07:00
tydude001andClaude Opus 5.5 6ad540e856 packaging: RPM requires library sonames, so openSUSE can install it
EffectCraft's RPM requires Fedora package names: `libxkbcommon`, `(vulkan-loader or libglvnd-egl)`,
`alsa-lib`. openSUSE has no `libxkbcommon`, `vulkan-loader` or `libglvnd-egl` package, so zypper
refuses it with `nothing provides 'libxkbcommon'` (exit 4, which the ArtCraft Launcher shows as
"error 4"; storytold/craft-launcher#341).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:30:14 -05:00
Hamza a213b7f23a i18n (de): fix two labels that name the wrong thing, settle six terms (#1443)
Two German menu labels described a different command:

- `layer.timeReverse` was "Ebenenreihenfolge umkehren", which reads as reversing
  the stacking order rather than time -- now "Ebene zeitlich umkehren", matching
  `keys.timeReverse`;
- `layer.style.convertToEditable` was "In editierbare Formate umwandeln", and
  "Formate" are formats, not layer styles -- now "In bearbeitbare Stile
  umwandeln".

Where the same English term had more than one German rendering, the one already
in the majority wins, so this settles rather than re-translates:

- "Expression" is "Ausdruck" in seven entries and "Expressions" in the two
  `layer.expressions` ones, which now read "Ausdrücke aktivieren/deaktivieren";
- "Reveal" is "anzeigen" in eleven of twelve entries, so `layer.revealInFinder`
  joins `file.revealInFinder` at "Im Finder anzeigen" -- in every other
  catalogue both commands already share one string;
- "Track" as a verb is "verfolgen" in the other four entries, so `track.mask`
  is "Maske verfolgen" rather than "Maske tracken";
- one spelling of "nach vorne", matching the neighbouring arrange entry.

German also had no platform variants for the file manager. The engine puts the
platform's name into the label before the catalogue sees it, so on Windows the
source is "Reveal in Explorer" and on Linux "Reveal in File Manager"; only
Ukrainian and Czech special-cased those, and a German UI showed the English
string. Added, with a test mirroring `czech_…` and `ukrainian_…`.

Two of the reported six are left alone: "Essential Graphics" is a 3-3 split and
a panel's proper name, and "Null" partly mirrors the English, which itself says
both "Null Object" and "Nulls". Both are translator's choices with no majority
to follow, not corrections.
2026-10-14 21:21:39 +05:00
Nigel Ihza Firdausy ba37283aa4 fix(lottie): serialize Offset Paths miter limits as animatable values 2026-10-14 23:14:18 +07:00
tylerjenningsw 8b536e597b Export: millisecond-rounded footage renders at its rate, each frame once (#1442) 2026-10-14 11:37:36 -04:00
tylerjenningsw 36a905293b Media: tests for the averaged MP4 frame rate (#1442) 2026-10-14 11:33:49 -04:00
tylerjenningsw eb95f732e5 Media: millisecond-rounded MP4 frame times read at the rate they average to (#1442)
Phones and messaging apps store frame times rounded to whole milliseconds
(33/34 ms at 30 fps, 16/17 ms at 60 fps). FilmCraft's reader takes the
median duration, so such clips imported at 30.303 or 58.824 fps, and New
Comp from Selection and renders followed that rate. When the median is no
standard rate, the durations near it are averaged and snapped to the
standard rate within 0.5 %; exact and variable rates keep the reader's
estimate.
2026-10-14 11:33:49 -04:00
zitann2020andDeepSeek V4.1 Flash 66a29505a5 i18n(ja): the Settings dialog in Japanese
Co-Authored-By: DeepSeek V4.1 Flash <noreply@deepseek.com>
2026-10-15 00:19:47 +09:00
midasmatterhorn c3b88d0d76 pdf: don't slice glyph names at a non-char boundary in name_to_unicode 2026-10-14 18:01:41 +03:00
wixar172andClaude Opus 5.5 9846c7255d Shapes: a stroke's buffer is padded by the miter limit only where it has miter joins
draw_bounds padded every stroke by width × miter limit / 2 on each side, joins or
not. A straight 1080 px wide wipe used as a track matte became a 4326×6366 layer
buffer (~440 MB as RGBA f32), made anew every frame while its trim animates, then
cached on the CPU and uploaded to the GPU. With 12 frames exported in parallel a
16 GB Mac ran out of memory and froze.

Now the miter limit applies only to miter-joined paths with a join (two or more
segments, or closed); square caps reach √2 half widths, other strokes one; Taper
and Wave keep the widest padding. Rendered pixels are unchanged.

A full-res frame of the affected ad: 1.7 GB → 0.7 GB peak.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:20:02 +03:00
Brandon ThomasandClaude Opus 5.5 05955c08b4 Release 0.10.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.10.0
2026-10-14 10:15:34 -04:00
Marco Arnulfo cc29d90943 Import: item.metadata counts a WebM's last frame when its duration is not a whole millisecond (fixes #1429) 2026-10-14 14:22:15 +02:00
Hamza 449e9edd59 Render Queue: resolve a bare project filename against the working directory
`resolve_output` takes the project's folder from `Path::new(path).parent()`.
For a project saved under a bare filename that is the empty path, which the
following `filter` discarded, so a saved project fell through to the
unsaved-project default and relative outputs went to ~/Movies (the user profile
folder on Windows) instead of next to the project.

    --project proj.ecproj      ~/Movies/clip.mp4
    --project ./proj.ecproj    ./clip.mp4

Both spellings name a project in the working directory, so both now resolve
there, as the doc comment on `resolve_output` already describes.

Closes #1427
2026-10-14 17:21:58 +05:00
Marco Arnulfo e26dd21af7 CLI: a failed render under run/exec exits 1, like render --queue (part of #1426) 2026-10-14 13:47:08 +02:00
Marco Arnulfo 78b4fe4170 Import: AIFF files import, including EffectCraft's own AIFF renders (fixes #1428) 2026-10-14 13:44:59 +02:00
Marco Arnulfo 69d17d89af Keyframes: a path pasted onto Position becomes a motion path (fixes #1425) 2026-10-14 13:30:45 +02:00
arnav-hiwarkar 68d928aff3 fix(render): preserve preceding effects in GPU Echo (#1120) 2026-10-14 13:42:59 +05:30
Marco Arnulfo 1b44c5c98a Scripting: Stroke and Hue/Saturation parameters use After Effects' match-name numbers (part of #1405) 2026-10-14 09:15:12 +02:00
arnav-hiwarkar bed289e2a1 fix(presets): identify copy folder action by label
Follow up on #1210: remove the fixed menu index from automation and click handling, and move the preset-folder documentation into the Effects & Presets section.
2026-10-14 12:09:48 +05:30
arnav-hiwarkar 8041329df8 feat(media-browser): add Copy Source Path 2026-10-14 11:42:13 +05:30
Moein Saharkhiz 217cd62402 Update contributor key: moecr7 -> onlymoein 2026-10-13 21:45:52 -07:00
Marco Arnulfo 0f0fef2b15 Contributors: add my real name 2026-10-14 05:28:41 +02:00
Marco Arnulfo ce437747ed Expressions: accepting a function completion inserts the call parentheses (fixes #1393) 2026-10-14 05:28:14 +02:00
sanyamsingh001andCodex fc3643ad17 Honor --empty when rendering from the CLI
Keep ordinary --empty renders from loading the demo. Preserve explicit
project, template, and demo selection, and cover them with real GIF renders.
Sort the relative-footage test's expected paths consistently with its output.

Co-authored-by: Codex <codex@openai.com>
2026-10-14 12:43:53 +10:00
tylerjenningsw 082bded546 Docs: architecture.md names the three particle systems that sample their Birth Rate (#1373) 2026-10-13 22:35:37 -04:00
tylerjenningsw 8d00db7d90 GPU: test CC Particle Systems II against the CPU with a keyed Birth Rate (#1373)
The keyed Birth Rate comparison is shared (keyed_agree) and now runs on
CC Particle Systems II too. The same two histories bear 90 particles by
0.9 s at different steps: the GPU's particles match the CPU's, and each
state really came from the GPU.
2026-10-13 22:35:37 -04:00
tylerjenningsw 6df1fc17b9 Render: tests for CC Particle Systems II and CC Mr. Mercury bursts and their layer cache (#1373)
Through the renderer, a keyed burst draws as the constant rate during it,
still draws after the rate drops to 0 (unlike a rate of 0 throughout),
and draws as nothing born once Longevity has run out. A Birth Rate
expression on any of the three stepped particle systems keeps the layer
out of the layer cache; without one it is cached.
2026-10-13 22:35:37 -04:00
tylerjenningsw 6fcfc355e3 Render: CC Particle Systems II and CC Mr. Mercury layers with expressions aren't layer cached (#1373)
They now read their Birth Rate at earlier times too, so
effects::reads_param_history names them along with CC Particle World. A
layer key can't see what an expression returned then.
2026-10-13 22:35:37 -04:00
tylerjenningsw 16cc290a47 Particles: tests for CC Particle Systems II and CC Mr. Mercury with a Birth Rate over time (#1373)
For both: a burst lives on after Birth Rate drops to 0, and is drawn
(particles, blobs) where a constant 0 draws nothing. Its particles move
and age, and are gone after their Longevity. Seeking matches a
simulation from 0, and moving an earlier key with the same current rate
and cache key isn't served from stale checkpoints. A constant rate gives
exactly the constant-rate simulation's particles, with or without a
host.
2026-10-13 22:35:36 -04:00
tylerjenningsw f4ab728c0a Particles: CC Particle Systems II and CC Mr. Mercury bear at the Birth Rate of each moment (#1373)
Both simulate from layer time 0, as CC Particle World does, and used
the Birth Rate of the rendered frame for every step. A rate keyed down
to 0 emptied them the frame it got there, although their particles (and
Mercury's blobs) were within their Longevity.

CC Particle World's simulation path becomes Stepped: Birth Rate sampled
at every step (rate_history), a cache key without it, and checkpoints
resumed only with the same rate history. Particle World, Particle
Systems II (60 particles a second per unit, CPU or GPU) and Mr. Mercury
(10 blobs a second per unit, CPU) now all use it. A constant Birth Rate
gives exactly the particles it did.

The constant-rate step, key and simulation (Phys::step, sim_key,
simulate) are now only the tests' reference. Mr. Mercury's physics move
into mercury_phys.
2026-10-13 22:35:36 -04:00
tylerjenningsw ba38397da4 Docs: architecture.md notes exact-time sampling and the layer cache for Particle World (#1373) 2026-10-13 21:59:04 -04:00
tylerjenningsw d04f88750e GPU: the keyed Birth Rate test tells birth timing from birth count and checks the GPU ran (#1373)
The two histories now bear the same 180 particles by 0.9 s at different
steps (3/s from 0 to 0.5 s, and from 0.25 to 0.75 s). So a checkpoint
check by birth count alone fails the test, not only no check. A
test-only dispatch counter asserts each GPU state really came from the
GPU rather than the CPU fallback.
2026-10-13 21:59:03 -04:00
tylerjenningsw f22ce3d839 Render: tests for a Hold Birth Rate cut under fractional stretch and an expression's history (#1373)
A Hold cut at 0.5 s reads 3 just before and 0 at 0.5 s under 33.3 %,
133.33 % and 200 % stretch, and the frame matches the unstretched one.
A Birth Rate expression reading a Controller layer's Opacity before
0.5 s: changing the Controller changes the 0.9 s frame, and the cached
render matches the uncached one.
2026-10-13 21:59:03 -04:00
tylerjenningsw aa300d7f2e Render: CC Particle World layers with expressions aren't layer cached (#1373)
The simulation checks its checkpoints against the Birth Rate history,
but a cached layer is returned before the effect runs. The layer key
sees the layer's keyframes and its current values, not what an
expression returned at earlier times: a Birth Rate expression reading
another layer could change the history while the current rate, 0, stayed
the same.

effects::reads_param_history names CC Particle World. The renderer
already declines to cache layers whose time effects (ec.time.*) meet
expressions. It now treats Particle World the same way, in the layer key,
the input key (only for the effects that input runs) and the dependency
walk.
2026-10-13 21:59:03 -04:00
tylerjenningsw f263a32ab7 Render: an effect parameter's history is read at its exact layer time (#1373)
FxHost::param_at mapped the requested layer time to comp time, and
EvalCtx::value mapped it back. Under a fractional stretch (33.3 %,
133.33 %) that round trip truncates a tick early, so a Hold key at
0.5 s read its previous value at the step starting there: a Birth Rate
cut to 0 at 0.5 s bore one more step of particles (six).

EvalCtx::value_at_layer_time reads the keyframes at the given layer
time exactly, and still runs an expression at the comp time.
EvalCtx::value now uses it, and param_at reads through it.

EffectHost::param_is_static says when a parameter has no keyframes and
no expression. CC Particle World then skips sampling its Birth Rate at
every step, since it's the same throughout.
2026-10-13 21:59:03 -04:00
tylerjenningsw 25fc51f3ed Timeline: Alt held as a pick whip drops refers by index (refs #1072)
A property or expression pick whip dropped with Alt held, onto the
Timeline or Effect Controls, now sends byIndex: an effect's property is
referred to by its index, as in After Effects.

Test: an_alt_pick_whip_refers_to_an_effect_property_by_index (Alt and
plain drops onto Effect Controls and the Timeline, into the expression
being edited with a dimension, one undo step each).
2026-10-13 21:57:19 -04:00
tylerjenningsw f2cdad9edb Expressions: pick-whip references to effect properties by index (refs #1072)
In After Effects, holding Alt while dropping the expression pick whip
refers to an effect's property by its index, effect("Color Control")(1),
instead of its name, ("Color").

prop.pickWhip takes byIndex: the effect keeps its name and each
property inside it is selected by its one-based place among its group's
children (nested groups too), which is what an expression's (1) reads.
Other properties, dimensions ([1]), insertion at the cursor and match
names (Compact English off) are as before. An index still reads a
renamed property, but another one if properties are reordered.

Tests: pick_whip_by_index_selects_effect_properties_by_their_index
(engine, the references); effectcraft-host pick_whip (by index and by
name read the same values, nested and per dimension).
2026-10-13 21:57:19 -04:00
tylerjenningsw c03cd79b59 Docs: the IndexedDB storage test (#1242) 2026-10-13 21:38:54 -04:00
tylerjenningsw f61f8c9958 Web: remembering and forgetting Media Browser folders waits for the commit too (#1242) 2026-10-13 21:38:54 -04:00
tylerjenningsw fa6c9ffccd Web: IndexedDB puts and deletes resolve when their transaction commits (#1242) 2026-10-13 21:38:54 -04:00
Marco Arnulfo 7df16a5e20 Effects: 4-Color Gradient keeps the layer's alpha with Blending Mode None (refs #1381) 2026-10-14 02:56:17 +02:00
tylerjenningsw 2b88724b43 Docs: architecture.md notes CC Particle World's Birth Rate history and checkpoint checks (#1373) 2026-10-13 20:45:21 -04:00
tylerjenningsw 1cea7d774f GPU: test GPU particles against the CPU with a keyed Birth Rate (#1373)
With a Birth Rate keyed into a burst, the GPU's particles match the CPU
simulation through the burst and after it. A second history with the
same system key, whose burst starts later (so the same ids are born at
other steps), isn't resumed from the first history's GPU checkpoints.
2026-10-13 20:45:21 -04:00
tylerjenningsw 8f03f24247 Render: tests for a CC Particle World burst through the renderer (#1373)
The issue's scene: a Birth Rate keyed 3 until 0.5 s then 0 matches the
constant rate during the burst, still draws the burst at 0.9 s with no
new births, and is empty once Longevity has run out. Moving the earlier
key changes the 0.9 s frame through the layer cache. Layers offset by
0.5 s or stretched 200 % sample their own layer times.
2026-10-13 20:45:20 -04:00
tylerjenningsw d5a1268ead CC Particle World: tests for a Birth Rate that changes over time (#1373)
A burst lives on after the rate drops to 0: as a constant rate during
it, only its particles afterwards, still moving, gone after their
Longevity. Another effect's history isn't used. A ramp bears what each
moment asked for. Seeking backward and forward matches a simulation
from 0. Moving an earlier key isn't served from stale checkpoints with
the same current rate. A constant rate is unchanged. The birth schedule
matches the CPU's ids.
2026-10-13 20:45:20 -04:00
tylerjenningsw bb0e2b57db CC Particle World: Birth Rate decides the births of its own time, so a burst lives on (#1373)
The simulation runs from layer time 0, but it used the Birth Rate of the
rendered frame for every step. A rate keyed from 3 down to 0 simulated
no births at all once it reached 0, so the burst's particles vanished
that frame although their Longevity had not run out. A ramped rate was
simulated as if its current value had always applied.

Each step now samples Birth Rate at its own layer time through
EffectHost::param_at (with the effect's own index). Without such a host
the current value applies throughout, as before. Particles already alive
keep moving and ageing whatever the rate. The carry and particle ids
continue across rate changes.

Birth Rate is left out of the simulation cache key, so an animated rate
no longer restarts the simulation every frame. In its place:

- The CPU state keeps a hash of the rates it was stepped with (hist). A
  checkpoint is resumed only when that matches the current history, so
  moving an earlier key is never served from a stale checkpoint.
- The GPU gets the birth schedule of the sampled rates (births_at). Its
  checkpoints record which births they hold, and are resumed only when
  the request's births before that step are the same.

A constant Birth Rate gives exactly the particles it did. CC Particle
Systems II and CC Mr. Mercury keep their constant-rate simulation.
2026-10-13 20:45:20 -04:00