This guide is intended for developers wishing to extend AVAA Toolkit functionalities via scripting.
Knowledge of HTML/JS is required.
This guide documents stable parts of the API, and acts as official specification for now.
Full API specification is expected to be defined and stable for 1.0 release.
Until then, any aspect of the current specification is subject to change, use at your own risk.
While AVAA Toolkit's core is made with Java, most features are actually implemented in JavaScript.
All these scripts are stored in the scripts folder and processed when necessary as the XML-to-HTML conversion advances.
The file name itself indicates the nature of the component, and its unique id.
This modularity allows anyone to create custom scripts to extend AVAA Toolkit and solve specific tasks, without the need for further core AVAA Toolkit development.
Scripts of common utility could then be shared by their authors and benefit the community at large.
Views are in charge of rendering a selection of annotations (or sometimes data) into HTML.
The view component is an object with the following structure:
// file view.my-custom-view.js
VIEW = {
list:list=>{
let html='';
for(a of list){
html+=`<div class="annotation">${a.tier.id}: ${a.value}</div>`;
}
return html;
},
css:()=>`
#view div {
color:green;
margin:10px;
border:2px dashed gray;
}
`
}
Note: in the css() method, any occurrence of #view will be replaced automatically by the actual view selector.
The view script file accepts these specific meta-definitions:
The view component runs with these objects available in scope:
Operations take input data and can eventually be effectful or return modified data.
The data usually is the current selection, an array of annotations.
The operation component is a simple function with the following arguments in order:
The operation function should return either an array of annotations, which will become the current selection, or any data except string.
In case a string is returned, it will be considered as another script to run in order to obtain the final effect/result (legacy behavior).
/**
@novalue
A simple operation to reverse an input array
**/
function(value, data){
if(!Array.isArray(data)){
log.error(`input must be an array`);
return data;
}
return data.reverse();
}
The operation script file accepts these specific meta-definitions:
The operation component runs with these objects available in scope:
Processors offer a wide range of possibilities, like manipulating the corpus or running external programs.
It is the component of choice for long-lasting tasks and advanced log feedback.
The processor component is an object with the following structure:
If the pipe method returns false, the pipeline execution will be aborted.
If an exception is raised during the pipe method call, the pipeline execution will be aborted.
PROC = {
pipe:pipe=>{
for(let f of pipe.corpus){
let step = log.addStep(f);
for(let mf of f.media){
step.file(mf, "Found media file:");
}
}
}
}
The processor script file accepts these specific meta-definitions:
The processor component runs with these objects available in scope:
Mods (from Modifiers) are special components with the ability to hook anywhere during HTML generation.
A mod can add, remove, or modify HTML code at each HTML output point.
This allows complete modification of the final HTML page, for instance to add interactive features or visual effects.
The mod component is an object with the following structure:
The following tag hooks are triggered:
The component must register itself on the available mod global object, for instance:
TODO
The mod script file accepts these specific meta-definitions:
The mod component runs with these objects available in scope:
Each script file can begin with doc comments /** ... **/ containing meta definitions.
A definition is a line starting with a keyword prefixed by the @ character.
The following definitions are common to all script files:
The following definitions make sense to some components:
If a line does not start with the @ character, it will be considered part of the component's description.
When a component is executed, some helper objects are available in its scripting scope.
The following objects are available in all the components:
The following objects are available depending on the component:
AVAA Toolkit can execute other programs and read their output, via the processor component.
The ScriptAPI exposes an exec(args:Array) method to prepare a process execution.
let step = log.addStep({name:`Getting ffmpeg version`});
let proc = api.exec(['ffmpeg','-version'])
proc.onError(msg=>{
step.error(`A process error occurred: ${msg}`)
})
proc.onOutput(msg=>{
step.info(msg)
})
// monitors execution and allows killing of the process from editor
step.monitor(proc);
// run the process and get its exit status
let exitStatus = proc.run();
let benched = Math.ceil(proc.duration/1000)+1;
if(!exitStatus){
step.info(`Completed in ${benched} seconds`);
step.ok();
}else{
step.error(`Process exited with error code ${exitStatus} after ${benched} seconds`);
}
It's possible to execute ffmpeg with the Generic Process approach, but it is cumbersome to read progress feedback and other results.
To this end, AVAA Toolkit provides a wrapper script that must be included in the component via the @dependency proc-ffmpeg-wrapper.js meta definition.
Then calling ffmpeg with
let step = log.addStep({name:`Custom Video Effect`});
let ffargs = [
'-i', inputFile.getAbsolutePath(),
'-filter_complex', `"gblur=sigma=42:steps=6;format=yuv420p"`,
outputFile.getAbsolutePath()
]
step.info(`Applying Gaussian Blur...`)
step.work()
let result = ffmpegWrapper(step, ffargs)
if(!result) throw `Failed applying the video filter!`
The wrapper has additional options not yet covered in this guide.
AVAA Toolkit also provides a wrapper and a protocol to simplify calling Python programs and reading their output.
Because it is not possible to understand the output of all potential Python programs, authors wishing to interface a Python program with AVAA Toolkit have to prefix its output according to the following rules:
print('#E:missing input')
print('#W:file will be overwritten')
print('#I:Temperature =', temperature)
print('#F:Input =', args.input)
print('#D:20%')
print('#P:6/50/Encoding')
print('#R:'+json.dumps(result))
Now to implement the processor that will call the Python program, it is necessary to include these two dependencies:
Let's see how this all fit with some code taken from the speech-to-text-whisper processor:
let req = log.addStep({name:'Requirements'})
let pythonVersion = pythonCheck(req);
if(!pythonVersion) return;
if(!pipCheck("torch",req)) return;
if(!pipCheck("openai-whisper",req)) return;
// checks that the avaa-whisper.py script is working
// the file avaa-whisper.py is in the dependencies folder
// it's a python script made to interface Whisper with AVAA
let whisperWrapper = pythonWrapper("avaa-whisper",req);
let step = log.addStep('Transcription');
let xargs = ['python','-u',whisperWrapper.getAbsolutePath(),
'-i',inputFile.getAbsolutePath(),
'-m',whisperModel,
'-t',whisperTemp,
'-l',whisperLang
];
let proc = api.exec(xargs);
step.monitor(proc);
proc.onError(s=>{
step.error(`Process Error: ${s}`)
});
// here we use the output wrapper
proc.onOutput(execOutputWrapper(step,result=>{
step.info(`Got results from transcription:`);
let a = JSON.parse(result);
// ...
}));
let exitStatus = proc.run();
AVAA Toolkit makes it easy to call R programs via the r-script processor.
The processor executes a R file (or R source code) and integrates the resulting data into the final HTML page. Resulting R output can be graphic files (jpg, png, gif, svg) or tabular text data. Arguments provided to the R script are in order:
R programs can send feedback messages to AVAA by following the same output wrapper protocol described in the Python section above.
When working directly with R source code (and not a R program file), the r-script processor will automatically add helper code:
args <- commandArgs(trailingOnly = TRUE)
avaaTempDir <- args[1]
avaaInput <- fromJSON(txt=args[2])
avaaTemp <- function(ext){
return(tempfile(pattern = "r-output-", fileext = paste(".",ext,sep=""), tmpdir = avaaTempDir))
}
avaaReturn <- function(output){
cat(paste("#R:",output,"\n", sep="")))
}
avaaLogFile <- function(f,s=''){
cat(paste("#F:",s," =", f, "\n", sep=""))
}
Example usage in source:
cat("#I:Demo Plot Script\n")
# Create x and y values
x <- 1:6
y <- x^2
# Plot to a png file
outputFile <- avaaTemp("png")
png(file = outputFile, width = 256*3, height = 256*3)
# Linear regression model y = A + B * x
model <- lm(y ~ x)
# Create a 2 by 2 layout for figures
par(mfrow = c(2, 2))
plot(model)
dev.off()
# Log and return file to AVAA
avaaLogFile(outputFile,'Plot File')
avaaReturn(outputFile)