Metadata-Version: 2.5
Name: svgly
Version: 0.1.4
Summary: Given some lilypond music in stdin, output optimized svg resulting from that music.
Project-URL: Homepage, https://gitlab.com/joaoseckler/svgly
Project-URL: Repository, https://gitlab.com/joaoseckler/svgly
Project-URL: Bug Tracker, https://gitlab.com/joaoseckler/svgly/issues
Author-email: João Seckler <jseckler@riseup.net>
License-Expression: GPL-3.0-or-later
License-File: LICENSE
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.13
Requires-Dist: beautifulsoup4>=4.15.0
Requires-Dist: cachetools>=6.2.6
Requires-Dist: jinja2>=3.1.6
Requires-Dist: lxml>=6.1.1
Requires-Dist: markupsafe>=3.0.3
Requires-Dist: numpy>=2.5.1
Requires-Dist: shelved-cache>=0.5.0
Requires-Dist: soupsieve>=2.9.1
Requires-Dist: svgpathtools>=1.7.2
Requires-Dist: svgwrite>=1.4.3
Description-Content-Type: text/markdown

## SVG Lilypond

Given some lilypond music in stdin, output optimized svg resulting from
that music.

Currently supports version 2.24.1 of lilypond (more recent version were
not tested. Older versions don't work).

### Installation

`pip install svgly`

or clone this repository and run `pip install .` or `uv sync`.

Make sure you have the necessary dependencies installed (see below).

### How it works

- Expect lilypond music as stdin
- Transform it into one (or multiple; see `--one-svg-per-measure`) svg
tag (`<svg ...> ... </svg>`)
- Allow for other options:
  - CSS styling
  - lilypond `\relative` to note

`$ svgly --help` yields:

```
usage: svgly [-h] [-k KEY] [-t TIME_SIGNATURE] [-c CLEF] [-r RELATIVE] [-p PAPER] [-l LAYOUT]
             [--padding PADDING] [--one-svg-per-measure] [--id-list ID_LIST]
             [--html-attribute ATTR=VALUE] [--hide-all] [-v] [--min-svg-width MIN_SVG_WIDTH]
             [--staff-size STAFF_SIZE] [--ly-block LY_BLOCK] [--all-keys] [--random-ids] [--use-cache]
             music [music ...]

Convert lilypond music to svg

positional arguments:
  music                 Lilypond music expression

options:
  -h, --help            show this help message and exit
  -k, --key KEY         Key signature
  -t, --time-signature TIME_SIGNATURE
                        Time signature
  -c, --clef CLEF       Clef
  -r, --relative RELATIVE
                        Relative of music expression
  -p, --paper PAPER     Paper options
  -l, --layout LAYOUT   Layout options
  --padding PADDING     Add padding to generated svg
  --one-svg-per-measure
                        Output one svg per measure. Only the first will containclef, key and time
                        signature. Measures are split by the character "|".
  --id-list ID_LIST     List of ids already taken, to avoid id duplication
  --html-attribute ATTR=VALUE
                        Add html attribute to top level svg
  --hide-all            Hide clef, key and time signature
  -v, --verbose         Print output of subprocesses
  --min-svg-width MIN_SVG_WIDTH
                        The minimum width of an generated svg, in pixels, when using --one-svg-per-
                        measure. If this options is set, this script will effectively generate one svg
                        for every few measures. Defaults to 0.
  --staff-size STAFF_SIZE
                        Choose the size of the staff. Default is 20
  --ly-block LY_BLOCK   Add arbitrary lilypond boilerplate
  --all-keys            Generate svgs transposed for all keys
  --random-ids          Change ids to use unique identifiers
  --use-cache           Use cache for calculations
```

This code can also be used as a module by using `from svgly import svgly, SVGLYOptions`
and calling (additional options are enabled when calling the function directly):

```python

from svgly import svgly, SVGLYOptions

# class SVGLYOptions:
#     music: str
#     key: str = r"c \major"
#     time_signature: str = "4/4"
#     clef: str = "treble"
#     relative: str = "c''"
#     paper: str = ""
#     layout: str = ""
#     padding: float = 0.0
#     one_svg_per_measure: bool = False
#     id_list: set[str] = field(default_factory=set)
#     html_attributes: dict[str, str] = field(default_factory=dict)
#     hide_all: bool = False
#     verbose: bool = False
#     min_svg_width: float = 0.0
#     staff_size: float = 20.0
#     ly_block: str = ""
#     all_keys: bool = False
#     random_ids: bool = False
#     use_cache: bool = False
#     ly_log_file: TextIO | None = None
#     use_docker: bool = False

svgly(SVGLYOptions(...))
```

#### Using docker

When using lilypond with user-input data, it is important to isolate the
it's execution environment. If `use_docker` is set, svgly will try to
use the `codello/lilypond` [docker image](https://github.com/codello/docker-lilypond)
to compile the svgs. It expects docker to be installed, and expects to
be able to sudo into it. To avoid needing root execution, add the
following to any sudoers file (e.g. `visudo /etc/sudoers.d/10-www-data-lilypond`):

```sudoers
www-data ALL=(root:root) NOPASSWD:/usr/bin/docker ^run -v /tmp/tmp[a-z0-9_]+:/work codello/lilypond --svg -o tmp tmp\.ly$
www-data ALL=(root:root) NOPASSWD:/usr/bin/chown ^www-data:www-data( /tmp[a-z0-9_]+/tmp(-[0-9]+)?.svg)+$
```

This assumes you are running with the www-data user and that `docker`
and `chown` are in the `/usr/bin` folder. Also needs `sudo` version >=
1.9.10 for regex support.

### Optimizing resulting HTML

Svgly generates a lot of repeated and uncecessary svg objects. After
building your html page with svgly, pass it through `htmlo` to
reduce its size. Informal experiments showed that for a 7 bar long
sheet, html reduces ~80% of it's size compared to regular svgly results.

From the command line, `htmlo --help` yields:

```
usage: htmlo [-h] [--wrap] [--random-ids]
             [--random-ids-length RANDOM_IDS_LENGTH] [--use-cache]
             input

Optimize SVGs in HTML

positional arguments:
  input                 Input HTML content (use - for stdin)

options:
  -h, --help            show this help message and exit
  --wrap                Wrap the input in a div
  --random-ids          Use random IDs for paths (avoiding collisions for
                        multiple optimizations in the same document)
  --random-ids-length RANDOM_IDS_LENGTH
                        If using random IDs, use this length for the
                        random IDs. Defaults to 4
  --use-cache           Use cached optimization
```

Typical usage:

`svgly - < music.ly | htmlo - > music.html`

Usage as library:

```python
from htmlo import optimize

content = "<svg>...</svg>"

optimize(
  content,
  wrap=True,
  random_ids=True,
  use_cache=True
)

```

### Why this

We use it to generate chords + lyrics + sheet music
at [Wikicifras](https://wikicifras.org).

### Dependencies

- python libs:
  - `jinja2`
  - `svgpathtools`
  - `bs4`
  - `cachetools`
  - `shelved_cache`
- LilyPond
- svgo
