Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5b7149e
docs(handoff): record the v1.3.1 release
Palawizard Oct 26, 2026
e182a05
docs(handoff): record the v1.3.1 release (#123)
Palawizard Oct 26, 2026
e3cf205
feat(ui): scroll the status columns on their own and list 12 clips
Palawizard Oct 27, 2026
efe0abe
feat(ui): follow the pointer while scrubbing and load one frame at a …
Palawizard Oct 27, 2026
6820501
feat(scripts): let the isolated app read clips from another folder
Palawizard Oct 27, 2026
520762a
test(scripts): measure the playhead while scrubbing in the isolated app
Palawizard Oct 27, 2026
97d9110
docs(docs): describe scrubbing without waiting for the video
Palawizard Oct 27, 2026
feb3abe
feat(engine): report the size and refresh rate of the captured source
Palawizard Oct 27, 2026
9173c4c
feat(ui): show the captured screen or window in the source cell
Palawizard Oct 27, 2026
0ad551b
docs(docs): document the source mode in the ipc contract
Palawizard Oct 27, 2026
3860bae
feat(ui): scroll the status columns on their own and list 12 clips (#…
Palawizard Oct 27, 2026
090a05d
feat(ui): follow the pointer while scrubbing and load one frame at a …
Palawizard Oct 27, 2026
a2d4829
feat(engine): play a distinct sound when a clip with an after part ends
Palawizard Oct 27, 2026
0dce2a1
docs(docs): document the clip finished sound and the save origin
Palawizard Oct 27, 2026
24438d8
feat(ui): split replay and quality settings into tabs
Palawizard Oct 27, 2026
175ae02
test(scripts): check the quality tabs in the isolated app
Palawizard Oct 27, 2026
23871cf
docs(docs): describe the replay and quality tabs
Palawizard Oct 27, 2026
04aad72
feat(ui): show the captured screen or window in the source cell (#126)
Palawizard Oct 27, 2026
a1acd57
chore(repo): merge dev into the clip finished sound branch
Palawizard Oct 27, 2026
ea6fab3
feat(engine): play a distinct sound when a clip with an after part en…
Palawizard Oct 27, 2026
5b43f7a
feat(ui): split replay and quality settings into tabs (#128)
Palawizard Oct 27, 2026
15d57f9
feat(library): keep the player mix of each clip
Palawizard Oct 27, 2026
0e177ab
feat(ui): mute the full mix during playback and remember the mix per …
Palawizard Oct 27, 2026
8db05b2
docs(docs): document the player mix setting and per clip state
Palawizard Oct 27, 2026
7ed1f52
chore(repo): merge dev into the full mix branch
Palawizard Oct 27, 2026
9e17b2d
test(scripts): check the player mix in the isolated app
Palawizard Oct 27, 2026
639d013
feat(ui): mute the full mix during playback and remember the mix per …
Palawizard Oct 28, 2026
dba50bb
test(scripts): prepare the manual session and the readme quality shot
Palawizard Oct 28, 2026
f0e6d7a
docs(docs): report v1.4 and refresh the readme
Palawizard Oct 28, 2026
8d2c5ad
chore(repo): prepare release v1.4.0
Palawizard Oct 28, 2026
3c5c0e9
chore(repo): prepare release v1.4.0 (#130)
Palawizard Oct 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,35 @@ Format : [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/), versions

## [Non publié]

## [1.4.0] - 2026-10-28

### Ajouté
- Lecteur : réglage « Couper le Mix complet à la lecture » (Général, activé par défaut). À
l'ouverture d'un clip qui a une piste Jeu ou une piste d'appli, la piste 1 est coupée pour ne
rien entendre en double ; elle se réactive avec M. L'état muet/solo/volume est retenu par
clip. L'export et la compression gardent la piste 1 tant qu'on ne l'a pas coupée soi-même
(GAL-04).
- Son « clip terminé » à la fin d'un clip avec « après » (La → Mi → La à l'octave), distinct du
carillon de l'appui et des sons d'enregistrement, soumis à « Son de confirmation » et exclu des
clips en « Tous les sons sauf PalaClips » (RPL-10). La fin d'un enregistrement ne joue plus le
carillon de sauvegarde par-dessus son propre son.
- Réglages → Replay & qualité en trois onglets (Replay, Enregistrement, Commun), chacun avec son
estimation de taille ; l'onglet est retenu pendant la session, un point marque l'onglet qui a
un changement non appliqué, et le résumé de l'enregistrement sur État ouvre l'onglet
Enregistrement (APP-15).

### Modifié
- État : en fenêtre large, « Derniers clips » et la colonne des pistes et performances défilent
chacun de leur côté sous le bandeau ; sous 1000 px, la page défile comme avant. 12 derniers
clips au lieu de 5.
- État : la cellule Source affiche la vraie source (« 2560×1440 · 240 Hz » pour un écran, la
taille de la fenêtre capturée pour une fenêtre), mise à jour en direct ; la qualité enregistrée
reste dans le résumé de la cellule Replay.
- Lecteur : pendant le glisser sur la barre de temps ou une piste, le curseur, le temps et la
zone jouée suivent la souris à chaque image ; l'image est chargée quand la souris s'arrête
(120 ms) et au relâchement, une demande à la fois. Sur un clip 1440p120 HEVC de 3 min :
2 chargements au lieu de 122, curseur redessiné toutes les 17 ms au lieu d'être figé (GAL-03).

## [1.3.1] - 2026-10-26

### Ajouté
Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ default-members = ["crates/engine", "crates/cli"]
exclude = ["third_party"]

[workspace.package]
version = "1.3.1"
version = "1.4.0"
edition = "2024"
rust-version = "1.90"
license = "MIT"
Expand Down
6 changes: 4 additions & 2 deletions README.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ fonctionne avec les anti-triches comme Easy Anti-Cheat.
- **Capture d'un écran ou d'une fenêtre** par Windows Graphics Capture ; une fenêtre est
retrouvée par le nom de son programme après un redémarrage.
- **Galerie de clips** avec un lecteur multipiste : écoute piste par piste (muet, solo,
volume), découpe, export, copie du fichier pour le coller dans Discord.
volume, retenus par clip ; Mix complet coupé à l'ouverture pour ne rien entendre en double),
déplacement fluide même sur un long clip HEVC, découpe, export, copie du fichier pour le
coller dans Discord.
- **Compression à une taille cible** : 20 Mo pour Discord, 50 ou 500 Mo pour Nitro, ou une taille
libre ; résolution et images par seconde choisies selon la taille, taille finale vérifiée.
Depuis la galerie (un ou plusieurs clips), le lecteur ou par glisser-déposer depuis
Expand All @@ -86,7 +88,7 @@ fonctionne avec les anti-triches comme Easy Anti-Cheat.

| Replay & qualité |
|---|
| ![Replay & qualité : taille d'un clip et Mo par minute, puis les réglages du replay, de l'enregistrement et communs](docs/screenshots/fr/replay-quality.png) |
| ![Replay & qualité en trois onglets (replay, enregistrement, commun), chacun avec son estimation de taille](docs/screenshots/fr/replay-quality.png) |

## Configuration requise

Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ Anti-Cheat.
Without a hardware encoder, **encoding on the processor** in H.264 or AV1.
- **Screen or window capture** through Windows Graphics Capture; a window is found again by
its program name after a restart.
- **Clip gallery** with a multitrack player: listen track by track (mute, solo, volume), trim,
export, or copy the file to paste it into Discord.
- **Clip gallery** with a multitrack player: listen track by track (mute, solo, volume,
remembered per clip; Full mix muted on opening so nothing plays twice), smooth scrubbing even
on long HEVC clips, trim, export, or copy the file to paste it into Discord.
- **Compress to a target size**: 20 MB for Discord, 50 or 500 MB for Nitro, or any size;
resolution and frame rate follow the size, and the final size is checked. From the gallery (one
or several clips), the player, or by dragging files from File Explorer.
Expand All @@ -84,7 +85,7 @@ Anti-Cheat.

| Replay & quality |
|---|
| ![Replay & quality: clip size and MB per minute, then the replay, recording and shared settings](docs/screenshots/en/replay-quality.png) |
| ![Replay & quality in three tabs (replay, recording, shared), each with its size estimate](docs/screenshots/en/replay-quality.png) |

## Requirements

Expand Down
4 changes: 2 additions & 2 deletions app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "palaclips-ui",
"private": true,
"version": "1.3.1",
"version": "1.4.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
57 changes: 56 additions & 1 deletion app/src-tauri/src/clips.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,37 @@ use palaclips_engine::library::{
Clip, ClipQuery, Library, TrackCache, TrackFile, copy_file_to_clipboard, move_to_recycle_bin,
track_peaks, watch,
};
use serde::Serialize;
use serde::{Deserialize, Serialize};
use tauri::{Emitter, Manager};

use crate::{EngineSlot, i18n, internal_error};

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct TrackMix {
pub volume: f32,
pub mute: bool,
pub solo: bool,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ClipMix {
pub tracks: Vec<TrackMix>,
pub full_mix_chosen: bool,
}

impl TrackMix {
fn bounded(self) -> Self {
Self {
volume: if self.volume.is_finite() {
self.volume.clamp(0.0, 1.0)
} else {
1.0
},
..self
}
}
}

pub const LIBRARY_EVENT: &str = "library-changed";
pub const EXPORT_PROGRESS_EVENT: &str = "export-progress";
pub const EXPORT_FINISHED_EVENT: &str = "export-finished";
Expand Down Expand Up @@ -180,6 +206,22 @@ impl EngineSlot {
self.with_library(|library| library.games())
}

pub fn clip_mix(&self, path: &str) -> Result<Option<ClipMix>, Localized> {
let path = self.known_clip(path)?;
let stored = self.with_library(|library| library.mix(&path))?;
Ok(stored.and_then(|json| serde_json::from_str(&json).ok()))
}

pub fn set_clip_mix(&self, path: &str, mix: ClipMix) -> Result<(), Localized> {
let path = self.known_clip(path)?;
let mix = ClipMix {
tracks: mix.tracks.into_iter().map(TrackMix::bounded).collect(),
..mix
};
let json = serde_json::to_string(&mix).map_err(|_| internal_error())?;
self.with_library(|library| library.set_mix(&path, &json))
}

pub fn set_clip_favorite(&self, path: &str, favorite: bool) -> Result<(), Localized> {
let path = self.known_clip(path)?;
self.with_library(|library| library.set_favorite(&path, favorite))
Expand Down Expand Up @@ -516,6 +558,19 @@ fn percent_decode(text: &str) -> Option<String> {
mod tests {
use super::*;

#[test]
fn a_stored_track_volume_stays_between_silence_and_full() {
let mix = |volume| TrackMix {
volume,
mute: false,
solo: true,
};
assert_eq!(mix(3.0).bounded().volume, 1.0);
assert_eq!(mix(-1.0).bounded().volume, 0.0);
assert_eq!(mix(f32::NAN).bounded().volume, 1.0);
assert!(mix(0.4).bounded().solo);
}

#[test]
fn ranges_are_clamped_to_the_file_and_to_one_chunk() {
assert_eq!(
Expand Down
30 changes: 29 additions & 1 deletion app/src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ use tauri::State;
use tauri::ipc::{Channel, InvokeResponseBody, Response};
use tauri_plugin_autostart::ManagerExt;

use crate::clips::ClipMix;
use crate::hotkeys::{HotkeyAction, Hotkeys};
use crate::i18n::{self, Language, LanguageSetting};
use crate::settings::{Feedback, Theme};
use crate::settings::{Feedback, Player, Theme};
use crate::{AppStatus, EngineSlot, internal_error};

#[tauri::command]
Expand Down Expand Up @@ -111,6 +112,16 @@ pub fn set_feedback(slot: State<'_, EngineSlot>, feedback: Feedback) -> Result<(
slot.update(|settings| settings.feedback = feedback)
}

#[tauri::command]
pub fn get_player(slot: State<'_, EngineSlot>) -> Player {
slot.player()
}

#[tauri::command]
pub fn set_player(slot: State<'_, EngineSlot>, player: Player) -> Result<(), Localized> {
slot.update(|settings| settings.player = player)
}

#[tauri::command]
pub async fn set_config(
app: tauri::AppHandle,
Expand Down Expand Up @@ -322,6 +333,23 @@ pub async fn clip_games(slot: State<'_, EngineSlot>) -> Result<Vec<String>, Loca
slot.clip_games()
}

#[tauri::command]
pub async fn clip_mix(
slot: State<'_, EngineSlot>,
path: String,
) -> Result<Option<ClipMix>, Localized> {
slot.clip_mix(&path)
}

#[tauri::command]
pub async fn set_clip_mix(
slot: State<'_, EngineSlot>,
path: String,
mix: ClipMix,
) -> Result<(), Localized> {
slot.set_clip_mix(&path, mix)
}

#[tauri::command]
pub async fn set_clip_favorite(
slot: State<'_, EngineSlot>,
Expand Down
Loading
Loading