7 Commits
Author SHA1 Message Date
kawa f380249c1c Merge branch 'main' of https://git.azuze.fr/kawa/Motionity
Sync Gitea releases to GitHub / sync-releases (push) Canceled after 0s
2026-08-13 12:47:42 +02:00
kawaandClaude Haiku 4.5 a87716dc5b feat: add credits modal and update docker config
- Add credits modal showing project creators and libraries used
- Replace bottom-of-canvas credits with clickable credits button
- Update docker-compose to pull pre-built image from git.azuze.fr registry
- Simplify README Docker instructions

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-13 12:26:10 +02:00
kawa 109f737c2a Update README.md
Sync Gitea releases to GitHub / sync-releases (push) Canceled after 0s
2026-08-13 11:43:11 +02:00
kawa d808c715fd Added the git wkflow
Sync Gitea releases to GitHub / sync-releases (push) Canceled after 0s
2026-08-13 09:04:04 +02:00
kawa 3ed8f5683f Fixed publish script+added options to separate binaries publishing.
Bumped package.json to 2.0.0
2026-08-13 08:48:34 +02:00
kawa fd0314549b Merge pull request 'Packaging/release scripts' (#1) from packaging/release-scripts into main
Reviewed-on: #1
2026-08-12 17:38:11 +02:00
kawa 89771b2555 docs: improve README with clearer structure and examples
Reorganize installation and build instructions with separate sections for
web, desktop, and Docker. Add quick start commands and move detailed
packaging info to PACKAGING.md.
2026-08-12 17:02:05 +02:00
9 changed files with 252 additions and 114 deletions
+72
View File
@@ -0,0 +1,72 @@
name: Sync Gitea releases to GitHub
on:
push:
branches:
- main
workflow_dispatch: {}
env:
GITEA_URL: https://git.azuze.fr
GITEA_OWNER: kawa
GITEA_REPO: Motionity
jobs:
sync-releases:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Install jq
run: sudo apt-get update && sudo apt-get install -y jq
- name: Fetch Gitea releases and sync to GitHub
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
AUTH_HEADER=()
if [ -n "${GITEA_TOKEN:-}" ]; then
AUTH_HEADER=(-H "Authorization: token ${GITEA_TOKEN}")
fi
# Récupère toutes les releases Gitea (pagination simple, 50 max, ajuster si besoin)
releases=$(curl -sf "${AUTH_HEADER[@]}" \
"${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases?limit=50")
echo "$releases" | jq -c '.[]' | while read -r release; do
tag=$(echo "$release" | jq -r '.tag_name')
name=$(echo "$release" | jq -r '.name // .tag_name')
body=$(echo "$release" | jq -r '.body // ""')
prerelease=$(echo "$release" | jq -r '.prerelease')
draft=$(echo "$release" | jq -r '.draft')
# Skip si la release existe déjà sur GitHub
if gh release view "$tag" >/dev/null 2>&1; then
echo "Release $tag existe déjà sur GitHub, on passe."
continue
fi
echo "Création de la release $tag sur GitHub..."
flags=()
[ "$prerelease" = "true" ] && flags+=(--prerelease)
[ "$draft" = "true" ] && flags+=(--draft)
# Le tag doit exister sur le repo GitHub ; s'il n'existe pas encore,
# on utilise --notes-file avec target par défaut (branche par défaut)
gh release create "$tag" \
--title "$name" \
--notes "$body" \
"${flags[@]}"
# Télécharge et attache les assets de la release Gitea
asset_count=$(echo "$release" | jq '.assets | length')
if [ "$asset_count" -gt 0 ]; then
tmpdir=$(mktemp -d)
echo "$release" | jq -c '.assets[]' | while read -r asset; do
asset_name=$(echo "$asset" | jq -r '.name')
asset_url=$(echo "$asset" | jq -r '.browser_download_url')
echo "Téléchargement de $asset_name..."
curl -sfL "${AUTH_HEADER[@]}" -o "${tmpdir}/${asset_name}" "$asset_url"
gh release upload "$tag" "${tmpdir}/${asset_name}" --clobber
done
rm -rf "$tmpdir"
fi
done
+21 -25
View File
@@ -1,38 +1,34 @@
# Motionity
### Demo available here : [motionity.kawa.zip](https://motionity.kawa.zip/)
Web-based motion graphics editor with keyframing, masking, filters and text animations.
This is a fork of the original project aiming to fix issues and add features.
This is a fork of the original [Motionity](https://github.com/alyssaxuu/motionity) by [@alyssaxuu](https://github.com/alyssaxuu), with bug fixes and enhancements.
## Running it
## Quick Start
**Web (localhost only):**
```bash
npm install # ffmpeg.wasm is vendored out of node_modules, so this is required
npm run vendor # downloads the third-party libraries into src/vendor/
npm start # http://127.0.0.1:8080
npm install
npm run vendor
npm start # http://127.0.0.1:8080
```
Three packaged distributions are available — desktop (Windows `.exe`, Linux
AppImage and Flatpak), a Docker image, and a bare-metal install. Build
instructions for all of them are in [PACKAGING.md](PACKAGING.md).
**Desktop:**
```bash
npm run dev # desktop app from source
npm run dist:win # Windows installer + portable exe
npm run dist:linux # AppImage + Flatpak
npm run docker:build # container image
npm install
npm run vendor
npm run dev # Electron app
```
## Releasing
```powershell
./scripts/build-release.ps1 # installers + SHA256SUMS.txt in dist/
./scripts/publish.ps1 -PublishRelease # + image push and Gitea release upload
**Docker:**
```bash
docker-compose up # http://localhost:8080
```
`build-release.ps1` needs no credentials. `publish.ps1` needs a Gitea token with
package read/write (image push) and `write:repository` (release upload), read
from `$env:GITEA_TOKEN` or prompted for. `-BinariesOnly -NoBinaryBuild` retries a
failed upload without rebuilding.
Full build instructions (Windows installers, Linux AppImage/Flatpak) in [PACKAGING.md](PACKAGING.md).
One rule applies to every target: browsers expose WebCodecs (the fast exporter)
and IndexedDB (project saving) only in a secure context. `http://localhost`
qualifies; a plain-HTTP LAN address does not. Serve it over TLS anywhere else.
## Notes
- `ffmpeg.wasm` is vendored; `npm install` + `npm run vendor` are required
- WebCodecs and IndexedDB only work in secure context (`http://localhost` OK, plain HTTP over LAN is not)
- Serve over TLS for remote access
+1 -7
View File
@@ -1,12 +1,6 @@
services:
motionity:
build:
context: .
# WITH_FFMPEG: "0" trims 18.5 MB by fetching the MP4/GIF encoder from
# archive.org at runtime instead of shipping it.
args:
WITH_FFMPEG: "1"
image: motionity:latest
image: git.azuze.fr/kawa/motionity:latest
ports:
- "8080:8080"
restart: unless-stopped
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "motionity",
"productName": "Motionity",
"version": "1.0.0",
"version": "2.0.0",
"description": "Web-based motion graphics editor with keyframing, masking, filters and text animations",
"license": "MIT",
"author": "Kawa",
+35 -12
View File
@@ -36,14 +36,19 @@
./scripts/build-release.ps1 -Targets win -Tag v1.1.0
Windows installers only, named v1.1.0.
.EXAMPLE
./scripts/build-release.ps1 -Targets win,linux-appimage
Windows installers plus the Linux AppImage — no Flatpak.
.EXAMPLE
./scripts/build-release.ps1 -SkipVendor -SkipDeps
Reuse src/vendor/ and node_modules as they are — the fast rebuild.
#>
[CmdletBinding()]
param(
# Platforms to package. "win" is NSIS + portable, "linux" is AppImage + Flatpak.
[ValidateSet("win", "linux")]
# Platforms to package. "win" is NSIS + portable; "linux-appimage" and
# "linux-flatpak" are the two Linux bundles, and "linux" is shorthand for both.
[ValidateSet("win", "linux", "linux-appimage", "linux-flatpak")]
[string[]]$Targets = @("win", "linux"),
# Version used in the artifact names. Defaults to v<package.json version>,
@@ -64,11 +69,15 @@ param(
$ErrorActionPreference = "Stop"
function Invoke-Checked {
param([Parameter(Mandatory)][string]$Exe, [Parameter(Mandatory)][string[]]$Args)
Write-Host " > $Exe $($Args -join ' ')" -ForegroundColor DarkGray
& $Exe @Args
# NB: the param is $CmdArgs, not $Args. $Args is an automatic variable in
# PowerShell; a param of that name never binds the passed array (it stays the
# function's own empty $args), so `& $Exe @Args` would run the exe with no
# arguments — e.g. bare `npm`, which just prints usage and exits 1.
param([Parameter(Mandatory)][string]$Exe, [Parameter(Mandatory)][string[]]$CmdArgs)
Write-Host " > $Exe $($CmdArgs -join ' ')" -ForegroundColor DarkGray
& $Exe @CmdArgs
if ($LASTEXITCODE -ne 0) {
throw "'$Exe $($Args -join ' ')' failed with exit code $LASTEXITCODE."
throw "'$Exe $($CmdArgs -join ' ')' failed with exit code $LASTEXITCODE."
}
}
@@ -101,11 +110,20 @@ try {
Write-Host " output : $distDir"
Write-Host ""
# Expand "linux" to its two concrete bundles and drop duplicates, so the rest
# of the script only ever deals with win / linux-appimage / linux-flatpak.
$resolvedTargets = @()
foreach ($t in $Targets) {
if ($t -eq "linux") { $resolvedTargets += "linux-appimage", "linux-flatpak" }
else { $resolvedTargets += $t }
}
$resolvedTargets = @($resolvedTargets | Select-Object -Unique)
# electron-builder produces AppImage and Flatpak with Linux-only tooling
# (appimagetool, flatpak-builder). Warned rather than blocked: the same script
# runs under pwsh on a Linux box or in WSL, which is where that target belongs.
if ($Targets -contains "linux" -and $env:OS -eq "Windows_NT") {
Write-Warning "the linux target needs a Linux host or WSL — electron-builder cannot produce AppImage or Flatpak on Windows (PACKAGING.md has the WSL setup)."
if (($resolvedTargets -like "linux-*") -and $env:OS -eq "Windows_NT") {
Write-Warning "the Linux targets need a Linux host or WSL — electron-builder cannot produce AppImage or Flatpak on Windows (PACKAGING.md has the WSL setup)."
}
if ($Clean) {
@@ -161,7 +179,7 @@ try {
throw 'electron-builder not found in node_modules — run "npm ci" (or drop -SkipDeps).'
}
foreach ($target in $Targets) {
foreach ($target in $resolvedTargets) {
Write-Host "Packaging $target..." -ForegroundColor Cyan
# --publish never: electron-builder otherwise tries to upload to whatever
@@ -175,10 +193,15 @@ try {
"-c.portable.artifactName=$(Get-ArtifactName "$prefix-win-x64-portable")"
)
}
"linux" {
"linux-appimage" {
$builderArgs = @(
"--linux", "AppImage", "flatpak", "--publish", "never",
"-c.appImage.artifactName=$(Get-ArtifactName "$prefix-linux-x86_64")",
"--linux", "AppImage", "--publish", "never",
"-c.appImage.artifactName=$(Get-ArtifactName "$prefix-linux-x86_64")"
)
}
"linux-flatpak" {
$builderArgs = @(
"--linux", "flatpak", "--publish", "never",
"-c.flatpak.artifactName=$(Get-ArtifactName "$prefix-linux-x86_64")"
)
}
+33 -15
View File
@@ -14,8 +14,10 @@
instead (creating the release if it does not exist).
-BinariesOnly ships just the installers: no docker build, no docker login, no
image push, and the release upload is implied. That is also the mode to use on
a Linux host or in WSL, where the AppImage and Flatpak targets actually build.
image push, and the release upload is implied. It takes the target list to
build (win, linux-appimage, linux-flatpak — comma-separated) and that list
overrides -Targets. The Linux targets need a Linux host or WSL, which is where
-BinariesOnly linux-appimage,linux-flatpak belongs.
Credentials are read, in order of precedence:
1. -Username / -Password parameters
@@ -35,13 +37,19 @@
Full release: push the image and attach every dist/ installer to release v1.1.0.
.EXAMPLE
./scripts/publish.ps1 -BinariesOnly -Targets win -Tag v1.1.0
./scripts/publish.ps1 -BinariesOnly win -Tag v1.1.0
Windows installers only — build them and attach them to release v1.1.0. Docker
is never invoked, so this works with Docker Desktop stopped.
.EXAMPLE
./scripts/publish.ps1 -BinariesOnly -NoBinaryBuild -Tag v1.1.0
Retry a failed upload: attach the installers already in dist/ without rebuilding.
./scripts/publish.ps1 -BinariesOnly win,linux-appimage -Tag v1.1.0
Windows installers plus the Linux AppImage (no Flatpak), attached to v1.1.0.
.EXAMPLE
./scripts/publish.ps1 -BinariesOnly win -NoBinaryBuild -Tag v1.1.0
Retry a failed upload: attach the installers already in dist/ without rebuilding
(the target list is required syntactically but ignored — every dist/ installer
for the tag is uploaded regardless).
.EXAMPLE
./scripts/publish.ps1 -NoBinaries
@@ -92,8 +100,8 @@ param(
# Skip building the desktop installers.
[switch]$NoBinaries,
# Forwarded to build-release.ps1.
[ValidateSet("win", "linux")]
# Forwarded to build-release.ps1. "linux" is shorthand for both Linux bundles.
[ValidateSet("win", "linux", "linux-appimage", "linux-flatpak")]
[string[]]$Targets = @("win", "linux"),
[switch]$SkipVendor,
@@ -103,7 +111,10 @@ param(
# Ship only the installers: no docker build, login or push. Implies
# -PublishRelease, since building alone is what build-release.ps1 already does.
[switch]$BinariesOnly,
# Takes the target list to build (comma-separated), which overrides -Targets:
# -BinariesOnly win,linux-appimage
[ValidateSet("win", "linux-appimage", "linux-flatpak")]
[string[]]$BinariesOnly,
# Attach the installers to the Gitea release for $Tag, creating the release if
# it is missing.
@@ -122,11 +133,13 @@ param(
$ErrorActionPreference = "Stop"
function Invoke-Checked {
param([Parameter(Mandatory)][string]$Exe, [Parameter(Mandatory)][string[]]$Args)
Write-Host " > $Exe $($Args -join ' ')" -ForegroundColor DarkGray
& $Exe @Args
# $CmdArgs, not $Args: $Args is a PowerShell automatic variable and never
# binds the passed array, so `& $Exe @Args` would run the exe bare.
param([Parameter(Mandatory)][string]$Exe, [Parameter(Mandatory)][string[]]$CmdArgs)
Write-Host " > $Exe $($CmdArgs -join ' ')" -ForegroundColor DarkGray
& $Exe @CmdArgs
if ($LASTEXITCODE -ne 0) {
throw "'$Exe $($Args -join ' ')' failed with exit code $LASTEXITCODE."
throw "'$Exe $($CmdArgs -join ' ')' failed with exit code $LASTEXITCODE."
}
}
@@ -283,20 +296,25 @@ $repoRoot = Split-Path -Parent $PSScriptRoot
Push-Location $repoRoot
try {
# --- Mode resolution ------------------------------------------------------
if ($BinariesOnly -and $NoBinaries) {
# -BinariesOnly is a target list, so its mere presence (a non-empty array) is
# what selects the mode.
$binariesOnlyMode = $BinariesOnly.Count -gt 0
if ($binariesOnlyMode -and $NoBinaries) {
throw "-BinariesOnly and -NoBinaries cancel each other out — pick one."
}
if ($NoBinaryBuild -and $NoBinaries) {
throw "-NoBinaryBuild reuses the build that -NoBinaries skips entirely — pick one."
}
if ($BinariesOnly) {
if ($binariesOnlyMode) {
# Nothing to build, log into or push on the container side, and uploading
# is the whole point (build-release.ps1 alone covers "just build them").
$NoBuild = $true
$SkipLogin = $true
$PublishRelease = $true
# The targets named on -BinariesOnly are what to build.
$Targets = $BinariesOnly
}
$pushImage = -not $BinariesOnly
$pushImage = -not $binariesOnlyMode
if (-not $ReleaseRepo) { $ReleaseRepo = "$Owner/$Repo" }
if (-not $ApiBase) { $ApiBase = "https://$Registry" }
+17 -2
View File
@@ -103,6 +103,22 @@
<p class="header-2">Export this project</p>
<div id="export-project"><img src="assets/download-icon.svg"> <span>Export</span></div>
</div>
<div id="credits-modal">
<p class="header">Credits</p>
<p class="subtitle">Created by <span>Alyssa X</span></p>
<p class="subtitle">Maintained by <span>Kawa</span></p>
<hr style="margin-top: 15px; margin-bottom: 15px;">
<p class="subheader">Libraries & Tools</p>
<div id="credits-list">
<div class="credit-item">Fabric.js</div>
<div class="credit-item">Anime.js</div>
<div class="credit-item">Lottie</div>
<div class="credit-item">Pickr</div>
<div class="credit-item">Sortable.js</div>
<div class="credit-item">FFmpeg.wasm</div>
<div class="credit-item">jQuery</div>
</div>
</div>
<div id="background-overlay"></div>
<div id="color-picker"></div>
<div id="color-picker-fill"></div>
@@ -290,8 +306,6 @@
</div>
</div>
<div id="bottom-canvas">
<a id="sponsor" href="https://github.com/sponsors/alyssaxuu" target="_blank"><img src="assets/sponsor.svg"> Sponsor</a>
<a id="alyssa-credit" href="https://twitter.com/alyssaxuu" target="_blank">Made by <span>Alyssa X</span> <img src="assets/alyssaimg.jpeg"></a>
</div>
<img src="assets/replace-image.svg" id="replace-image">
<img src="assets/loading-image.svg" id="load-image" class="load-media">
@@ -375,6 +389,7 @@
<div id="share"><img src="assets/importexport.svg"> Import & export</div>
<div id="download"><img src="assets/download-icon.svg"> Download</div>
</div>
<div id="credits-button">Credits</div>
</div>
<video id="test-video"></video>
+8
View File
@@ -1785,6 +1785,14 @@ function importExportModal() {
}
$('#share').on('click', importExportModal);
// Open credits modal
function creditsModal() {
hideModals();
$('#credits-modal').toggleClass('modal-open');
$('#background-overlay').toggleClass('modal-open');
}
$('#credits-button').on('click', creditsModal);
function searchInput() {
var value = $(this).val().toLowerCase();
if (value == '') {
+64 -52
View File
@@ -204,6 +204,27 @@ body {
.hand-active:hover {
cursor: pointer;
}
#credits-button {
width: 100px;
position: absolute;
right: 330px;
bottom: 12px;
height: 38px;
line-height: 38px;
text-align: center;
background-color: #30314e;
color: var(--main-text-color);
border-radius: 5px;
box-shadow: inset 0px 2px 2px rgba(255, 255, 255, 0.05),
inset 0px -2px 1px rgba(0, 0, 0, 0.05);
font-family: Inter;
font-weight: 500;
font-size: 14px;
}
#credits-button:hover {
cursor: pointer;
opacity: 0.7;
}
/* Bottom of canvas */
#bottom-canvas {
position: absolute;
@@ -212,58 +233,6 @@ body {
z-index: 9999999;
width: 100%;
}
#sponsor {
position: absolute;
bottom: 10px;
left: -10px;
height: 32px;
line-height: 32px;
font-family: Inter;
font-size: 14px;
font-weight: 500;
color: var(--accent-color);
background: rgba(22, 95, 205, 0.1);
text-align: center;
text-decoration: none;
padding-left: 10px;
padding-right: 10px;
border-radius: 5px;
}
#sponsor img {
margin-right: 3px;
margin-bottom: -2px;
}
#sponsor:hover {
background: rgba(22, 95, 205, 0.3) !important;
}
#alyssa-credit {
color: var(--secondary-text-color);
font-size: 14px;
font-weight: 500;
position: absolute;
right: -20px;
bottom: 15px;
font-family: Inter;
width: 200px;
filter: drop-shadow(0px 1px 15px #141629);
text-decoration: none !important;
}
#alyssa-credit:hover {
filter: drop-shadow(0px 1px 10px #141629) !important;
}
#alyssa-credit span {
color: var(--main-text-color) !important;
text-decoration: none !important;
display: inline-block;
}
#alyssa-credit img {
display: inline-block;
margin-left: 5px;
border-radius: 50%;
vertical-align: middle;
width: 18px;
margin-top: -2px;
}
.hide-folder {
display: none !important;
}
@@ -1375,6 +1344,49 @@ input[type="number"] {
display: block;
z-index: 99999999999;
}
/* Credits modal */
#credits-modal {
width: 300px;
max-height: 400px;
background-color: var(--panel-back);
border: 1px solid var(--panel-stroke);
border-radius: 5px;
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.3);
position: absolute;
right: 280px;
bottom: 60px;
visibility: hidden;
display: block;
z-index: 99999999999;
padding-bottom: 15px;
overflow-y: auto;
}
#credits-modal .header {
margin-bottom: 15px;
}
#credits-modal .subtitle {
margin-left: 20px;
margin-bottom: 5px;
font-size: 13px;
}
#credits-modal .subtitle span {
color: var(--main-text-color) !important;
font-weight: 500;
}
#credits-modal .subheader {
margin-top: 10px;
margin-bottom: 10px;
}
#credits-list {
margin-left: 20px;
margin-right: 20px;
}
.credit-item {
color: var(--secondary-text-color);
font-family: Inter;
font-size: 13px;
margin-bottom: 8px;
}
.subtitle {
color: var(--secondary-text-color);
font-family: Inter;