This article introduces how to develop plugins for vLabeler
.
Feel free to ask questions or make feature requests if you find the plugin specification not sufficient for your
intended functionality.
A plugin for vLabeler
is a folder containing:
- A
plugin.json
file to define the plugin's behaviors - At least one
*.js
file as the scripts - Other files that your scripts use
plugin.json
file is a JSON object which has the following properties:
Property | Type | Default value | Supported plugin type | Description |
---|---|---|---|---|
name | String | (Required) | all | Make sure the value is the same as the folder's name. |
version | Integer | 1 | all | |
type | String | (Required) | all | template or macro . |
displayedName | String (Localized) | same as name |
all | |
author | String | (Required) | all | |
String | "" | all | ||
description | String (Localized) | "" | all | |
website | String | "" | all | |
supportedLabelFileExtension | String | (Required) | all | Extension(s) of your label file (e.g. ini for UTAU oto). "*" and "|" are supported. |
outputRawEntry | Boolean | false | template | Set to true if you want to output the raw entry instead of parsed object. |
scope | String | "Module" | all | Module or Project . The scope that the plugin can access and edit. |
parameters | Parameters | null | null | all | See the Defining Parameters section for detail. |
scriptFiles | String[] | (Required) | all | File names of all your scripts files. The files will be executed in the same order as declared. |
resourceFiles | String[] | [] | all | List of String. File names of all the files that you use as resources in your scripts. The contents will be passed to your scripts as string values in the same order as declared. |
inputFinderScriptFile | String | null | null | template | File name of the script file to help find the input files dynamically. |
parameters
object contains an array named list
:
{
...,
"parameters": {
"list": [...]
},
...
}
Every object in list
defines a parameter that is shown in the plugin config dialog and passed to your scripts.
The object has the following properties:
Property | Type | Default value | Supported parameter type | Description |
---|---|---|---|---|
type | String | (Required) | all | Can be any one of integer , float , boolean , string , enum and ,entrySelector . |
name | String | (Required) | all | Parameter name for reference in your scripts. |
label | String (Localized) | (Required) | all | Displayed in the config dialog. |
description | String (Localized) | "" | all | Displayed in the config dialog. |
enableIf | String | null | null | all | If set, this parameter is enabled only when the parameter with the set name is truthy. |
defaultValue | (Actual type of the value) | (Required) | all | Value type is according to the parameter's type . |
min | (Actual type of the value) | null | integer, float | |
max | (Actual type of the value) | null | integer, float | |
multiLine | Boolean | false | string | Set to true if you want to allow multi-line string values. |
optional | Boolean | false | string, file, rawFile | Set to true if you want to allow empty string values or null file |
options | String[] | (Required) | enum | Items of the enumerable. |
optionDisplayedNames | String[] (Localized) | null | null | enum | Displayed names of the corresponding items in options . If set null , options itself is used. |
acceptExtensions | String[] | null | null | file, rawFile | Extensions of the files that can be selected. If set null , any file can be selected. |
integer
: Integer value. Should be betweenmin
andmax
if defined.float
: Float value. Should be betweenmin
andmax
if defined.boolean
: Should be eithertrue
orfalse
.string
: String value. Should not contain line breaks ifmultiLine
isfalse
. Should not be empty ifoptional
isfalse
. You can setdefaultValue
tofile::path/to/file
to load the file's content as the default value.enum
: Enumerable value described as string. Should be one of the items inoptions
.entrySelector
: Can only be used in amacro
type plugin. Instance of EntrySelector type. For detailed usage, see section Use an entry selector. An example of valid values follows:
{
"filters": [
{
"type": "text", // an example of a `text` filter
"subject": "name", // `name` for entry name or `sample` for sample name
"matchType": "Contains", // `Contains` | `Equals` | `StartsWith` | `EndsWith` | `Regex`
"matcherText": "foo"
},
{
"type": "number", // an example of a `number` filter
"subject": "overlap", // `name` of any property defined in the labeler
"matchType": "GreaterThan", // `Equals` | `GreaterThan`| `LessThan` | `GreaterThanOrEquals` | `LessThanOrEquals`
"comparerValue": 0.5, // only used when `comparerName` is null
"comparerName": "offset" // nullable, `name` of any property defined in the labeler
}
]
}
file
: Instance of FileWithEncoding. In the scripts, instead of the object itself, the file's content will be passed as a string value. An example of valid values follows:
{
"file": "path/to/file", // can be null or omitted
"encoding": "UTF-8" // can be null or omitted
}
rawFile
: File path as string. In the scripts, it's passed as a string value without file reading.
vLabeler
uses embedded JavaScript engine provided
by GraalVM 22.1.
It implements JavaScript in the ECMAScript (ECMA-262) specification which is fully compatible with the ECMAScript 2021 specification (ES12). Check JavaScript Compatibility for detailed info about language specifications.
A plugin with template
type is selected and executed on the New Project
page.
It should create a list of entries for subsequent editions.
The following variables are provided before your scripts are executed.
name | type | description |
---|---|---|
inputs | String[] | List of texts read from the input files. They are provided by the input finder script |
samples | String[] | List of file names of the sample files. |
params | Dictionary | Use name of the defined parameters as the key to get values in their actual types. |
resources | String[] | List of texts read from the resources files in the same order as declared in your plugin.json . |
labeler | LabelerConf | Equivalent Json object to LabelerConf object. |
labelerParams | Dictionary | Use name of the defined parameters in current labeler as the key to get values in their actual types. |
debug | Boolean | It's set to true only when the application is running in the debug environment (Gradle run task). |
pluginDirectory | File | Directory of this plugin |
To support your template plugin with labelers that construct a project with subprojects, you may want to find input
files dynamically for every subproject. To do this, you can provide a script file via the
plugin's inputFinderScriptFile
property in the plugin.json
.
The script file should be a JavaScript file which:
- takes a variable
root
ofFile
type as input, which indicates the root directory of the project, i.e. theSample Directory
set in the project creation page. - takes a variable
moduleName
ofString
type as input, which indicates the name of the subproject. - takes variables
debug
,labeler
,params
as input, which are the same as the ones provided in the template generation scripts. - should set a variable
inputFilePaths
ofString[]
type as output, which contains all the absolute paths of the input files you want to use. - may set a variable
encoding
ofString
type as output, which indicates the encoding of the input files. If not set, the encoding selected in the project creation page is used.
The File
type is a JavaScript wrapper of Java's java.io.File
class. See the documentation for
details.
Check the audacity2lab plugin for an example. In this example, for consistency, even if the project has only one subproject, the input files are still found by the input finder script, so that we don't have to care about where the input comes from in the main script.
If your input file does not belong to a subproject (such as a dictionary file), you can use a parameter of type file
or rawFile
to get the content.
See Parameter Type for details.
You have to create a list named output
to pass the result back to the application.
You can choose to offer output
in the following two ways:
Put items in the following type to the output
list (the class is defined before your scripts are executed):
class Entry {
constructor(sample, name, start, end, points, extras, notes = new Notes(), needSync = false) {
this.sample = sample // sample file name
this.name = name // entry name (alias)
this.start = start // float value in millisecond
this.end = end // float value in millisecond
this.points = points // list of float values in millisecond
this.extras = extras // list of string values
this.notes = notes // info including "done", "starred" and tag
this.needSync = needSync // set true if the entry needs to be updated with the sample's length,
// typically for UTAU entries with non-negative cutoff values.
}
}
class Notes {
constructor(done = false, star = false, tag = "") {
this.done = done
this.star = star
this.tag = tag
}
}
Please check LabelerConf.kt for details about its properties.
If outputRawEntry
is set to true
, instead of the above class, output
should be set to a list of strings in the
format of the label file. They will be parsed by the labeler's parser later.
- If
labeler.allowSameNameEntry
is set tofalse
, the labeler will discard entries with the same name except the first one. Handle the duplicated entry names in your scripts if you want to keep them. - The labeler will throw an error if no entry is created. Create a fallback entry in your scripts if you don't want users to see the error.
- If a project is created by labeler's parser with a raw label file, it will include all sample files even if they do not appear in the raw label file. In that case, a default entry will be created for each sample file. However, if a project is created by a plugin, the sample files that are not referenced in the output will be ignored. So if you want to cover every sample file, do it in your scripts.
Check the following built-in template
plugins as examples:
- ust2lab-ja-kana: Use an input ust file to generate sinsy lab entries
- cv-oto-gen: Generate CV oto entries from parameters
- regex-raw-gen: Use a regular expression to generate raw entry lines. Supports all types of labelers.
- audacity2lab: Generate lab entries from an audacity label file.
It also supports the
NNSVS singer labeler
which constructs a project with subprojects. See Find input files dynamically for details.
A plugin with macro
type is executed by an item in the menu Tools
-> Batch Edit
. It is only available when editing
a project.
According to the macroScope
property in the plugin.json
, the plugin can be executed on the whole project or on the
current module (subproject).
If macroScope
is set to Module
, the input includes a list of Entry
object in the current module named entries
along with an integer named currentEntryIndex
.
If macroScope
is set to Project
, the input includes a list of Module
object named modules
along with an integer
named currentModuleIndex
.
You can modify these variables directly to conduct batch edit on the whole project.
The following variables are provided before your scripts are executed.
name | type | description |
---|---|---|
entries | Entry[] | Only available when the plugin's macroScope is Module . List of current Entry objects in the current module. |
currentEntryIndex | Entry[] | Only available when the plugin's macroScope is Module . The index of current shown entry. |
module | Module | Only available when the plugin's macroScope is Module . The current Module object. |
modules | Module[] | Only available when the plugin's macroScope is Project . List of current Module objects in the project. |
currentModuleIndex | Integer | Only available when the plugin's macroScope is Project . The index of current shown module. |
params | Dictionary | Use name of the defined parameters as the key to get values in their actual types. |
resources | String[] | List of texts read from the resources files in the same order as declared in your plugin.json . |
labeler | LabelerConf | Equivalent Json object to LabelerConf object. |
labelerParams | Dictionary | Use name of the defined parameters in current labeler as the key to get values in their actual types. |
debug | Boolean | It's set to true only when the application is running in the debug environment (Gradle run task). |
pluginDirectory | File | Directory of this plugin |
A parameter with entrySelector
type is passed in params
as a list of the selected indexes of the entries
list.
The following code is an example of using an entry selector:
let selectedIndexes = params["selector"] // use actual name of your parameter
for (let index of selectedIndexes) {
let entry = entries[index]
// edit the entry
}
Change the content of the given entries
or modules
list to change the project's content.
Note that you can access to module
if the scope is Module
, but the changes to it will not be reflected.
The following code is an example of adding a suffix to every entry's name in the current module:
let suffix = params["suffix"]
// This is `Module` scope, so `entries` is available
for (let entry of entries) {
entry.name += suffix
}
This feature is only available of macro
plugins.
You can show a report after the plugin is executed successfully by calling report(message)
.
The parameter message
can be a string or a localized string.
See the Localization section for more details.
// display report in default language
report("This is a report.")
// display report in multiple languages
report({
en: "This is a report in English.",
zh: "这是中文的报告。"
})
This feature is only available of macro
plugins.
You can ask vLabeler to conduct audio playback after the plugin is executed successfully by
calling requestAudioFilePlayback(path, offset = 0, duration = null)
.
See the source code for more details.
Check the following built-in macro
plugins as examples:
- batch-edit-entry-name: Edit selected entry names for all labelers. You can refer to it for the usage of the entry selector
- batch-edit-oto-parameter: Edit parameters of selected
entries for UTAU oto. The
labeler
is used to get the specific settings aboutoto.ini
- compare-oto-entries: Compare an input oto with the current
project. You can refer to it for the usage of the
report()
function - execute-scripts: Execute input scripts to edit the project. It can be used as a debug tool when developing a plugin.
- resampler-test: Test the resampler synthesis of the current entry.
You can refer to it for the usage of the
requestAudioFilePlayback()
function andEnv
,File
,CommandLine
APIs
- Env: For getting some environment information, such as OS name.
- File: For accessing files.
- CommandLine: For executing command line programs.
All the properties with a String (Localized)
type can be provided in multiple languages.
For example, if you want to provide a localized description, you can do it like this:
{
...,
"description": {
"en": "This is a description in English.",
"zh": "这是中文的描述。"
},
...
}
It's totally optional, so you can still provide only the default language en
:
{
...,
"description": "This is a description in English.",
...
}
An option in the map is used when the current language code starts with its language code.
e.g. If the current language code is "en-US", the entry with key "en" is used. So it's recommended to use common
language codes like en
and zh
instead of en-US
and zh-CN
.
Please note that you have to provide a default language en
in your localization map, otherwise the plugin gets a
parse error when being loaded.
Specially, the enum
type parameter also supports localized option names by setting the optional property
optionDisplayedNames
:
{
...,
"defaultValue": "option1",
"options": [
"option1",
"option2",
"option3"
]
"optionDisplayedNames": [
{ en: "Option 1", zh: "选项1" },
{ en: "Option 2", zh: "选项2" },
{ en: "Option 3", zh: "选项3" }
],
...
}
When the scripts encounter illegal inputs, you can show an error message to users by calling error(message)
.
The parameter message
can be a string or a localized string.
See the Localization section for more details.
Other errors thrown in the scripts will be displayed as "Unexpected errors" without detailed information, indicating that it is more likely to be a bug of the plugin, rather than an illegal input or something else that may happen in normal use cases.
If a user contacts you with an "Unexpected errors", you can ask for detailed information in the logs to help you solve the issue.
let unknownExpressionMatch = expression.match(/\$\{\w+}/)
if (unknownExpressionMatch) {
// throwing error in default language
error(`Unknown placeholder: ${unknownExpressionMatch[0]}`)
// throwing error in multiple languages
error({
en: `Unknown placeholder: ${unknownExpressionMatch[0]}`,
zh: `未知的占位符: ${unknownExpressionMatch[0]}`
})
}
You can use logs to help debug your scripts.
The standard output (e.g. print()
) is written to .logs/info.log
and the error output is written to
.logs/error.log
.
If the plugin is not shown in the list, there are probably some errors while loading the plugin (i.e.
parsing plugin.json
).