Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
b8d15ef
refactor: hardcoded values
FelipeDefensor Jul 26, 2025
27f7e75
chore: auto-format
FelipeDefensor Jul 27, 2025
914a0bd
refactor: remove unused code
FelipeDefensor Aug 14, 2025
6964fdd
fix: do not usable mutable default argument
FelipeDefensor Aug 26, 2025
7c3f233
fix: crash when creating score annotation
FelipeDefensor Sep 5, 2025
3be8cee
fix: crash when loading svg viewer without beat timeline
FelipeDefensor Nov 7, 2025
24576bd
fix: restore state with identical components but different ids
FelipeDefensor Nov 6, 2025
f07fd4d
test: record state after creating timelines with fixtures
FelipeDefensor Nov 13, 2025
974e715
refactor: rename Post.APP_RECORD_STATE to Post.APP_STATE_RECORD
FelipeDefensor Nov 13, 2025
467cb5e
test: set timeline visibility
FelipeDefensor Nov 7, 2025
4f4be13
fix: color validator
FelipeDefensor Nov 7, 2025
769e30b
refactor: start using {Timeline,TimelineUI}.subclasses instead of tim…
FelipeDefensor Aug 26, 2025
f13fa08
refactor: use Timeline.flags instead of individual class attributes
FelipeDefensor Aug 26, 2025
4c534a8
refactor: commands APIS [MAJOR]
FelipeDefensor Nov 25, 2025
63abd6b
fix: detection of empty timelines
FelipeDefensor Nov 12, 2025
d33d581
refactor: take "time" parameters to on_add method of point-like Timel…
FelipeDefensor Nov 12, 2025
1a31ad5
test: fix errors on test_dirs.py
FelipeDefensor Nov 25, 2025
ae05c63
fix: duplicated error message
FelipeDefensor Nov 26, 2025
7e64d1f
fix: error message wording
FelipeDefensor Nov 26, 2025
45dd1da
docs: add debug tip
FelipeDefensor Nov 26, 2025
0fae6ec
fix: undo/redo when importing
FelipeDefensor Nov 26, 2025
cfa4ab0
refactor: remove dead code
FelipeDefensor Nov 26, 2025
7074f76
fix: toggle timeline visibility
FelipeDefensor Feb 3, 2026
817a0ad
dev: clearer "Unregistered command" error output
FelipeDefensor Feb 5, 2026
b13eb27
fix: preserve timeline ids when opening files and undoing/redoing
FelipeDefensor Feb 5, 2026
cae09c6
fix: do not crash on race condition for creation of dirs.SITE_DATA_DIR
FelipeDefensor Feb 5, 2026
0b99b59
test: use deepdiff to print mismatching app states
FelipeDefensor Feb 5, 2026
6eb1f2a
fix: crash when using timeline context menus
FelipeDefensor Feb 5, 2026
b90e4a0
fix: crash when deleting beat timeline after score timeline has been …
FelipeDefensor Feb 5, 2026
1c5a8ad
fix: "//" in YouTube URL being replaced with "/" when saving
FelipeDefensor Feb 10, 2026
6d9a8a2
refactor: use patch_yes_or_no instead of Serve
FelipeDefensor Feb 17, 2026
1b6af29
fix: crash when timeline folder doesn't have the right structure
FelipeDefensor Feb 27, 2026
8500205
test: fix failing tests on Linux and macOS
FelipeDefensor Feb 27, 2026
c28cf26
test: fix failing tests on Linux and macOS
FelipeDefensor Feb 27, 2026
fb912be
refactor: remove unused posts
azfoo Mar 4, 2026
9db3142
refactor: actually call the post
azfoo Mar 4, 2026
7108397
refactor: remove duplicate
azfoo Mar 4, 2026
946d599
refactor: remove unused gets
azfoo Mar 4, 2026
9633a12
refactor: remove unused tl selectors
azfoo Mar 4, 2026
9bfb4c4
refactor: correct method name
azfoo Mar 4, 2026
305f6d5
fix: restore missing inspector value
azfoo Mar 4, 2026
66944b5
refactor: actually delete the file
azfoo Mar 6, 2026
609ee14
refactor: remove unused methods
azfoo Mar 12, 2026
ab51fd3
refactor: typing
azfoo Mar 12, 2026
bbaa5c7
docs: typo
azfoo Mar 12, 2026
e73be76
test: add helper functions for saving and opening tilia files
FelipeDefensor Mar 13, 2026
9b48a08
fix: duplicated IDs when using previously saved files
FelipeDefensor Mar 13, 2026
8b77a65
fix: restore deleted get
azfoo Mar 13, 2026
a87067d
refactor: boolean comparison
FelipeDefensor Mar 17, 2026
39f54f7
fix: error message on failed command with a partial for a callback
FelipeDefensor Dec 22, 2025
23c2eda
dev: improve unregistered command error message
FelipeDefensor Mar 17, 2026
df61c27
refactor: boolean comparison
FelipeDefensor Mar 17, 2026
eff94b9
dev: improve docstring
FelipeDefensor Mar 17, 2026
26ebdf7
perf: id generation
FelipeDefensor Mar 17, 2026
0793fd5
fix: crash when timeline command callback was not a function
FelipeDefensor Mar 17, 2026
a9654aa
fix: invalid return values for timeline commands not being detected
FelipeDefensor Mar 17, 2026
09d3d76
perf: import timeline subclasses only once
FelipeDefensor Mar 18, 2026
3251835
refactor: remove unused TimelineFlag
FelipeDefensor Mar 18, 2026
0b2fca4
refactor: always set TimelineFlags explicitly
FelipeDefensor Mar 18, 2026
72bcd07
fix: color value validator
FelipeDefensor Mar 18, 2026
f5536d9
fix: media path = `.` when no media is loaded
FelipeDefensor Feb 21, 2026
a21a630
fix: dynamically set audio outputs
FelipeDefensor Mar 16, 2026
7c4f049
fix: comments icon visibility
FelipeDefensor Mar 10, 2026
1093fdc
refactor: remove CLI command
FelipeDefensor Feb 6, 2026
17b7390
refactor: more consistent folder structure for CLI tests
FelipeDefensor Feb 6, 2026
758b271
feat: output warning in CLI
FelipeDefensor Feb 7, 2026
b2f3c66
feat: raise warning when getting timelines by a duplicated name
FelipeDefensor Feb 7, 2026
d20d5e8
refactor: move `permute_ordinal` to backend
FelipeDefensor Feb 7, 2026
037816a
refactor: add cli.io.error function
FelipeDefensor Feb 7, 2026
d11d24e
feat: add move CLI command
FelipeDefensor Feb 13, 2026
2d0d497
docs: mention CLI in feature list
FelipeDefensor Feb 13, 2026
a62041f
docs: improve CLI help strings
FelipeDefensor Feb 12, 2026
fb10a39
docs: add tutorial for CLI
FelipeDefensor Feb 13, 2026
aa615af
refactor: use refactored function
FelipeDefensor Feb 17, 2026
0161c35
refactor: use simpler API for tests
FelipeDefensor Feb 17, 2026
47e45ca
docs: add help hints
azfoo Mar 13, 2026
48677d5
docs: correct command
azfoo Mar 13, 2026
d9ffab3
refactor: reorder
azfoo Mar 13, 2026
bc97da3
fix: remove instructions for running CLI from binary
FelipeDefensor Mar 17, 2026
bff16d4
docs: point to cli help
azfoo Mar 17, 2026
8bf1f6b
feat: preserve label and comments from merged units
FelipeDefensor Mar 10, 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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Here are some examples TiLiA visualizations:
- Toggling of timeline visibility
- Export of audio segments based on analysis
- Import timeline data from CSV files
- Command-line interface (see the CLI [help page](docs/cli-tutorial.md) for more information)

## How to install and use
TiLiA has releases for Windows, macOS and Linux. Instructions to download and run are [at the website](https://tilia-app.com/help/introduction/).

Expand Down
8 changes: 5 additions & 3 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ The test suite is written in pytest. Below are some things to keep in my mind wh
Unfortunately, we can't simulate input to modal dialogs, as they block execution. To work around that, we can:
- Mock methods of the modal dialogs (e.g. `QInputDialog.getInt`). There are utility functions that do that in some cases (e.g. `tests.utils.patch_file_dialog`)
- If the dialog is called in response to a `Get` request, the `Serve` context manager can be used to mock the return value of the request. E.g.:

```python
with Serve(Get.FROM_USER_INT, (True, 150)):
user_actions.trigger(TiliaAction.TIMELINE_HEIGHT_SET)
commands.execute("timeline_height_set")
```

We should prefer the first option as it makes the test cover more code, but the second is more resilient to changes in implementation details.
Expand All @@ -31,10 +32,11 @@ def test_me(tlui, marker_tlui):
```

can be rewritten as:

```python
def test_me(marker_tlui, user_actions, tilia_state):
def test_me(marker_tlui, tilia_state):
tilia_state.current_time = 0
user_action.trigger(TiliaAction.MARKER_ADD)
commands.execute("marker_add")
assert not len(marker_tlui) == 1
```
You will find many examples of the former in the test suite, though. Refactors are welcome.
Expand Down
356 changes: 356 additions & 0 deletions docs/cli-tutorial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,356 @@
# TiLiA CLI Tutorial

This tutorial shows how the TiLiA CLI can be used to perform some common tasks.

### Setup

See [README.md](README.md) for instructions on how to clone the repository and install the dependencies.

### Running the CLI

The CLI can be run from the source code with:
```bash
python -m tilia.main --user-interface cli
```

TiLiA CLI is an interactive shell. You'll see ``>>>`` when it's ready for your input.

### Getting Help

For any command, you can get detailed help and examples by typing a command followed by `--help`:

```bash
>>> timelines add --help
usage: main.py timelines add [-h] [--name NAME] [--height HEIGHT] [--beat-pattern BEAT_PATTERN [BEAT_PATTERN ...]] {hierarchy,hrc,marker,mrk,beat,bea,score,sco}

positional arguments:
{hierarchy,hrc,marker,mrk,beat,bea,score,sco}
Kind of timeline to add

options:
-h, --help show this help message and exit
--name NAME, -n NAME Name of the new timeline
--height HEIGHT, -e HEIGHT
Height of the timeline
--beat-pattern BEAT_PATTERN [BEAT_PATTERN ...], -b BEAT_PATTERN [BEAT_PATTERN ...]
Pattern as space-separated integers indicating beat count in a measure. Pattern will be repeated. Pattern '3 4', for instance, will alternate measures of 3 and 4 beats.

Examples:
timelines add beat --name "Measures" --beat-pattern 4
timelines add hierarchy --name "Form"
timelines add marker --name "Cadences"
```

---

## Creating a New Project from Media

These commands can be used to create a TiLiA project, load a media file, create timelines, and organize them.

Load a media file:
```bash
load-media "C:/path/to/audio.mp3" # Local file
load-media "https://www.youtube.com/watch?v=..." # YouTube video
```

Set the media duration manually:
```bash
metadata set-media-length 300 # 300 seconds = 5 minutes
```

Set some basic metadata about the piece:
```bash
metadata set title "Bohemian Rhapsody"
metadata set composer "Freddie Mercury"
metadata set performer "Queen"
```

Display metadata with:
```bash
metadata show
```
The output should be something like this:
```bash
Title: Bohemian Rhapsody
Notes:
Composer: Freddie Mercury
Performer: Queen
...
Media length: 300.0
```

Now that a media is loaded, and metadata is set, we can create timelines:
```bash
timelines add beat --name "Measures" --beat-pattern 4
timelines add hierarchy --name "Form"
timelines add marker --name "Cadences"
```

To display existing timelines use:
```bash
timelines list
```
This should output:
```bash
+------+----------+-----------+
| ord. | name | kind |
+------+----------+-----------+
| 1 | | Slider |
| 2 | Measures | Beat |
| 3 | Form | Hierarchy |
| 4 | Cadences | Marker |
+------+----------+-----------+
```

Note that you must have a media loaded or a manually set duration to create timelines.

## Organizing Timelines

After creating timelines, you might want to reorder or remove some.

```bash
timelines add hierarchy --name "Form"
timelines add marker --name "Themes"
timelines add beat --name "Measures" --beat-pattern 4
timelines add marker --name "Cadences"
timelines list

# Output
+------+----------+-----------+
| ord. | name | kind |
+------+----------+-----------+
| 1 | Form | Hierarchy |
| 2 | Themes | Marker |
| 3 | Measures | Beat |
| 4 | Cadences | Marker |
+------+----------+-----------+
```

Reorder timelines:

```bash
# Move "Measures" to the top (position 1)
timelines move name "Measures" 1

# Move the timeline at position 4 (Cadences) to position 2
timelines move ordinal 4 2
```

Remove a timeline we don't need anymore:
```
timelines remove name "Themes"
```

Check the final arrangement:
```
timelines list

# Output
+------+----------+-----------+
| ord. | name | kind |
+------+----------+-----------+
| 1 | Measures | Beat |
| 2 | Cadences | Marker |
| 3 | Form | Hierarchy |
+------+----------+-----------+
```

## Importing Annotations from CSV

If you have annotations in CSV format (perhaps made with another tool), you can import them.

First, let's look at the CSV files we'll import:

**form.csv:**
```csv
start,end,level,label,comments
0.0,30.0,1,Introduction,
30.0,90.0,1,Verse 1,
35.0,50.0,2,Phrase A,
50.0,65.0,2,Phrase B,
90.0,150.0,1,Chorus,
```

**cadences.csv:**
```csv
time,label,comments
29.5,HC,Half Cadence
89.3,PAC,Perfect Authentic Cadence
149.8,PAC,
```

For detailed CSV format specifications, see: https://tilia-app.com/help/import

To import the CSV files:

```bash
# Set up the project
load-media "C:/audio/song.mp3"
timelines add hierarchy --name "Form"
timelines add marker --name "Cadences"

# Import hierarchies from CSV (using time values)
timelines import hierarchy by-time --file "C:/data/form.csv" --target-name "Form"

# Import markers from CSV (using time values)
timelines import marker by-time --file "C:/data/cadences.csv" --target-name "Cadences"

```
When you have beat/measure information, you can import annotations using measure numbers instead of absolute time.

Here we will also be importing beats from a CSV file, but they could come from an existingTiLiA file.

**beats.csv:**
```csv
time
0.0
0.6
1.2
1.8
2.4
3.0
```
Hierarchy timeline CSV data:

**form_measures.csv:**
```csv
start_measure,start_fraction,end_measure,end_fraction,level,label,comments
1,0.0,8,0.0,1,Introduction,
9,0.0,24,0.0,1,Verse,
9,0.0,16,0.0,2,Phrase A,
17,0.0,24,0.0,2,Phrase B,
```

Now import them:

```bash
# Set up with a beat timeline
load-media "C:/audio/piece.mp3"
timelines add beat --name "Measures" --beat-pattern 4
timelines add hierarchy --name "Form"

# Import beat data first
timelines import beat --file "C:/data/beats.csv" --target-name "Measures"

# Now import hierarchies using measure numbers
timelines import hierarchy by-measure --file "C:/data/form_measures.csv" --target-name "Form" --reference-tl-name "Measures"

```

## Working with Existing TiLiA files

You can also open and modify existing TiLiA files.

Open an existing file:
```bash
open "C:/projects/song.tla"
timelines list

# Output
+-----+----------+-----------+
| ord. | name | kind |
+-----+----------+-----------+
| 1 | Measures| Beat |
| 2 | Form | Hierarchy |
| 3 | Cadences| Marker |
+-----+----------+-----------+
```

Now let's add a new timeline and import some data into it:

```bash
timelines add marker --name "Dynamics"
timelines import marker by-time --file "C:/data/dynamics.csv" --target-name "Dynamics"
save "C:/projects/song.tla" --overwrite
```

We can also export the file to JSON for use in other tools:

```bash
export "C:/exports/song.json" --overwrite
```

---

## Automation with CLI Scripts

You can create a `.txt` file with a sequence of commands to automate repetitive tasks, so you don't have to type them every time.

**process_song.txt**
```
# Load and set up project
load-media "C:/audio/song.mp3"
metadata set title "Example Song"
metadata set composer "John Doe"

# Create timeline structure
timelines add beat --name "Measures" --beat-pattern 4
timelines add hierarchy --name "Form"
timelines add marker --name "Cadences"

# Import all annotations
timelines import beat --file "C:/data/beats.csv" --target-name "Measures"
timelines import hierarchy by-measure --file "C:/data/form.csv" --target-name "Form" --reference-tl-name "Measures"
timelines import marker by-measure --file "C:/data/cadences.csv" --target-name "Cadences" --reference-tl-name "Measures"

# Save the result
save "C:/projects/song.tla" --overwrite
```

Then run it from the CLI with the `script` command:

```bash
# Run the script
script "C:/scripts/process_song.txt"
```

**Script features:**
- Lines starting with `#` are comments and are ignored
- Empty lines are ignored
- Commands execute sequentially
- Script stops on first error

For processing multiple files, you can use any programming language to generate a single CLI script containing commands for each file.

**batch_process.py**:

```python
from pathlib import Path

# Configuration
audio_dir = Path("C:/audio")
data_dir = Path("C:/data")
output_dir = Path("C:/projects")
script_file = Path("C:/temp/tilia_script.txt")

# Files to process
songs = ["song1", "song2", "song3"]
script_content = ""

for song in songs:
# Generate CLI script
script_content += f"""
# Process {song}
load-media "{audio_dir / song}.mp3"
metadata set title "{song}"

# Create timelines
timelines add beat --name "Measures" --beat-pattern 4
timelines add hierarchy --name "Form"
timelines add marker --name "Cadences"

# Import data
timelines import beat --file "{data_dir / song}_beats.csv" --target-name "Measures"
timelines import hierarchy by-measure --file "{data_dir / song}_form.csv" --target-name "Form" --reference-tl-name "Measures"
timelines import marker by-measure --file "{data_dir / song}_cadences.csv" --target-name "Cadences" --reference-tl-name "Measures"

# Save and clear
save "{output_dir / song}.tla" --overwrite
clear --force
"""

# Write script
with open(script_file, 'w') as f:
f.write(script_content)

```
Loading
Loading