-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.R
448 lines (378 loc) · 17 KB
/
server.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
#' SERVER for a Shiny App to facilitate verification of audio classification
#' Simon Gillings BTO
#' April 2023
#imports
require(audio)
require(shinyjs)
require(tuneR)
require(signal)
require(scales)
require(shiny)
require(shinyFiles)
require(shinyalert)
require(ini)
#read and parse the ini settings
settings <- parse_settings(read.ini('settings.ini'))
settings$max_clip_duration <- 10
#get the drive letters
volumes <- getVolumes()()
# Define server logic required to draw a histogram
server <- function(input, output, session) {
shinyjs::hide('settings_panel')
shinyjs::hide('file_picker')
shinyjs::hide('btn_scan')
shinyjs::hide('spec')
shinyjs::hide('controls')
shinyjs::hide('checks')
shinyjs::hide('file_count')
shinyjs::hide('file_current')
shinyjs::hide('spec_settings')
shinyjs::hide('btn_undo')
global <- reactiveValues(
path_audio = 'Path not set',
files_audio = NULL,
n_files = 0,
file_counter = NULL,
file_current = NULL,
label = NULL,
history = history_start
)
shinyDirChoose(input, 'path_audio', roots = volumes, session = session, filetypes = c(''))
#' OBSERVERS ------------------------------------------------------------------------
#show/hide the filename
observe({
if(input$show_filename == TRUE) shinyjs::show('file_current')
if(input$show_filename == FALSE) shinyjs::hide('file_current')
})
#show the history button?
observe({
if(nrow(global$history) > 0) shinyjs::show('btn_undo')
if(nrow(global$history) == 0) shinyjs::hide('btn_undo')
})
#observer to hold the current custom label
observe({
global$label <- input$custom_label
})
#observer for the audio folder selection
observeEvent(eventExpr = {input$path_audio},
handlerExpr = {
#clear any existing settings
global$files_audio <- NULL
global$file_counter <- NULL
global$file_current <- NULL
#and hide mainpanel stuff until file picked
shinyjs::hide('spec')
shinyjs::hide('controls')
shinyjs::hide('checks')
shinyjs::hide('file_count')
shinyjs::hide('file_current')
shinyjs::hide('file_picker')
global$path_audio <- parseDirPath(volumes, input$path_audio)
shinyjs::show('btn_scan')
} )
#observer to show/hide the settings for the spectrogram
observeEvent(eventExpr = input$spec_settings, toggle('settings_panel'))
#observer for the main start button
# check dir exists and that audio there
# update global reactive to include list of files, number of files
# update and show the file picker dropdown
# show the main panel image and controls
observeEvent(eventExpr = input$btn_scan,
handlerExpr = {
files <- list.files(global$path_audio, pattern = "*.wav", full.names = FALSE)
#randomise file order?
if(input$random_order == TRUE) {
files <- sample(files, size = length(files))
}
if(length(files)==0) {
#message('No such path for audio')
shinyalert(title = "Error",
text = "No audio files at this location",
type = "error",
callbackR = message('Callback: No such path for audio')
)
}
if(length(files) > 0) {
addResourcePath("audio", global$path_audio)
global$files_audio <- files
global$n_files <- length(files)
if(file_dropdown) updateSelectInput(session, input = "file_picker", choices = files, selected=files[1])
global$file_counter <- 1
nav_button_toggles(global$n_files, global$file_counter)
global$file_current <- global$files_audio[global$file_counter]
#make/overwrite history dataset
global$history <- history_start
shinyjs::show('file_picker')
shinyjs::show('spec')
shinyjs::show('controls')
shinyjs::show('checks')
shinyjs::show('file_count')
if(input$show_filename==TRUE) shinyjs::show('file_current')
shinyjs::show('spec_settings')
}
})
#observer for the file picker dropdown
observeEvent(eventExpr = input$file_picker, {
global$file_current <- input$file_picker
global$file_counter <- which(global$files_audio == input$file_picker)
})
#observers for the file navigation buttons
observeEvent(input$btn_first, {
global$file_counter <- 1
global$file_current <- global$files_audio[global$file_counter]
nav_button_toggles(global$n_files, global$file_counter)
})
observeEvent(input$btn_previous, {
global$file_counter <- max(1, global$file_counter - 1)
global$file_current <- global$files_audio[global$file_counter]
nav_button_toggles(global$n_files, global$file_counter)
})
observeEvent(input$btn_next, {
global$file_counter <- min(global$file_counter + 1, global$n_files)
global$file_current <- global$files_audio[global$file_counter]
nav_button_toggles(global$n_files, global$file_counter)
})
observeEvent(input$btn_last, {
global$file_counter <- global$n_files
global$file_current <- global$files_audio[global$file_counter]
nav_button_toggles(global$n_files, global$file_counter)
})
#observer for the undo button
observeEvent(input$btn_undo, {
#get the last entry of history
#last_item <- history[nrow(history),]
last_item <- global$history[nrow(global$history),]
#call the audio move function to move the clip back
moved_back <- audio_move(path_from = last_item$path_to,
path_to = last_item$path_from,
file_from = last_item$file_to,
file_to = last_item$file_from
)
#if the file was successfully moved back:
if(moved_back==TRUE) {
#remove the last row from history
#history <<- history[1:nrow(history)-1,]
global$history <- global$history[1:nrow(global$history)-1,]
#refresh file list and number of files
files <- list.files(global$path_audio, pattern = "*.wav", full.names = FALSE)
#randomise file order?
if(input$random_order == TRUE) {
files <- sample(files, size = length(files))
}
#and put the focal file back in position 1
files <- append(files[-which(files==last_item$file_from)],
files[which(files==last_item$file_from)], 0)
global$files_audio <- files
global$n_files <- length(files)
#refresh current file
global$file_current <- last_item$file_from
#refresh file counter
global$file_counter <- which(files==last_item$file_from)
#update dropdown
if(file_dropdown) updateSelectInput(session, input = "file_picker", choices = global$files_audio, selected=global$file_current)
#update nav buttons
nav_button_toggles(global$n_files, global$file_counter)
}
if(moved_back==FALSE) stop("Error: Failed to undo file move")
})
observeEvent(input$btn_custom, {
if(is.null(input$label)) {
shinyalert(title = "Error",
text = "There is no label in the custom label input field",
type = "error",
callbackR = message('Callback: No label in input field')
)
}
if(!is.null(input$label)) {
#get the codes for these species
selected_species <- subset(splist, select_val %in% input$label)
#construct the label string
label <- paste0('-[',paste0("'", selected_species$code, "'", collapse = ','), '].wav')
#clear the value from the select
updateSelectizeInput(inputId = 'label', selected = " ")
#make new file name
file_new <- gsub(".wav", label, global$file_current)
#move the file and rename
renamed <- audio_move(path_from = global$path_audio,
path_to = file.path(global$path_audio, 'labelled'),
file_from = global$file_current,
file_to = file_new
)
#if file successfully moved
if(renamed==TRUE) {
#add record to history
h1 <- data.frame('path_from' = global$path_audio,
'path_to' = file.path(global$path_audio, 'labelled'),
'file_from' = global$file_current,
'file_to' = file_new,
stringsAsFactors = FALSE)
#history <<- rbind(history, h1) #<< to add to global history
global$history <- rbind(global$history, h1) #<< to add to global history
#print(history)
#remove file from list
global$files_audio <- global$files_audio[global$files_audio != global$file_current]
#reduce number of files by one
global$n_files <- global$n_files - 1
#update file_current if still files to check and not on last file in list
if(global$n_files > 0 & global$file_counter <= global$n_files) {
global$file_current <- global$files_audio[global$file_counter]
#update dropdown
if(file_dropdown) updateSelectInput(session, input = "file_picker", choices = global$files_audio, selected=global$file_current)
}
#update file_current if still files to check and on last file in list
if(global$n_files > 0 & global$file_counter > global$n_files) {
#must reduce counter by one more to move it back a step to the last remaining file
global$file_counter <- global$file_counter - 1
global$file_current <- global$files_audio[global$file_counter]
#update dropdown
if(file_dropdown) updateSelectInput(session, input = "file_picker", choices = global$files_audio, selected=global$file_current)
}
#no files left
if(global$n_files == 0) {
global$file_counter <- 0
global$file_current <- NULL
#update dropdown
if(file_dropdown) updateSelectInput(session, input = "file_picker", choices = NULL, selected=NULL)
shinyjs::hide('file_count')
shinyjs::hide('file_current')
shinyalert(title = "Success",
text = "All files in this folder have been verified",
type = "success",
callbackR = message('Callback: No more audio in this folder')
)
}
}
}
})
#' OUTPUTS ----------------------------------------------------------------------
#show the path
output$path_audio <- renderText({ global$path_audio })
#dynamic creation of a set of validation buttons based on value list in settings panel
obsList <- list() # to store observers and make sure only once is created per button
output$validation_buttons <- renderUI({
val_but_list <- unlist(strsplit(settings$vfchoices, ','))
#val_but_list <- unlist(strsplit(input$validation_buttons_list, ','))
val_but_list <- trimws(unique(val_but_list))
buttons <- as.list(1:length(val_but_list))
buttons <- lapply(buttons, function(i)
{
btName <- paste0("validation_button_",i)
# creates an observer only if it doesn't already exists
if (is.null(obsList[[btName]])) {
# make sure to use <<- to update global variable obsList
obsList[[btName]] <<- observeEvent(input[[btName]], {
#cat("Button ", i, "pressed\n")
moved <- audio_move(path_from = global$path_audio,
path_to = file.path(global$path_audio, val_but_list[i]),
file_from = global$file_current,
file_to = global$file_current
)
if(moved==TRUE) {
#add record to history
h1 <- data.frame('path_from' = global$path_audio,
'path_to' = file.path(global$path_audio, val_but_list[i]),
'file_from' = global$file_current,
'file_to' = global$file_current,
stringsAsFactors = FALSE)
#history <<- rbind(history, h1) #<< to add to global history
global$history <- rbind(global$history, h1) #<< to add to global history
#print(history)
#remove file from list
global$files_audio <- global$files_audio[global$files_audio != global$file_current]
#reduce number of files by one
global$n_files <- global$n_files - 1
#update file_current if still files to check and not on last file in list
if(global$n_files > 0 & global$file_counter <= global$n_files) {
global$file_current <- global$files_audio[global$file_counter]
#update dropdown
if(file_dropdown) updateSelectInput(session, input = "file_picker", choices = global$files_audio, selected=global$file_current)
}
#update file_current if still files to check and on last file in list
if(global$n_files > 0 & global$file_counter > global$n_files) {
#must reduce counter by one more to move it back a step to the last remaining file
global$file_counter <- global$file_counter - 1
global$file_current <- global$files_audio[global$file_counter]
#update dropdown
if(file_dropdown) updateSelectInput(session, input = "file_picker", choices = global$files_audio, selected=global$file_current)
}
#no files left
if(global$n_files == 0) {
global$file_counter <- 0
global$file_current <- NULL
#update dropdown
if(file_dropdown) updateSelectInput(session, input = "file_picker", choices = NULL, selected=NULL)
shinyjs::hide('file_count')
shinyjs::hide('file_current')
shinyalert(title = "Success",
text = "All files in this folder have been verified",
type = "success",
callbackR = message('Callback: No more audio in this folder')
)
}
}
})
}
actionButton(inputId=btName,
label=paste(val_but_list[i]),
style="background-color: #337ab7; color: #fff; margin-top:5px;margin-bottom:5px;")
}
)
})
#make the spectrogram
output$spec <- renderPlot({
if(global$file_counter>0) {
file1 <- file.path( global$path_audio, global$file_current )
#read the wav data using audio preferably, but if this fails with an odd incomplete file error, use tuneR
wavdata <- tryCatch(
{
# Try to load the wave file using audio::load.wave
audio::load.wave(file1)
},
error = function(e) {
# If an error occurs, try using tuneR::readWave
message("audio::load.wave failed, attempting tuneR::readWave...")
tuneR::readWave(file1)
}
)
#unpack depending on which method is used
if(class(wavdata)=='Wave') {
#just use left channel for now
signal <- wavdata@left
sr <- [email protected]
}
if(class(wavdata)!='Wave') {
signal <- wavdata
sr <- signal$rate
#deal with stereo file - dim is null if mono
if(!is.null(dim(signal))) {
signal <- apply(signal,2,mean)
}
}
if(length(signal)/sr > settings$max_clip_duration) {
shinyalert(title = 'Error: clip too long',
text = paste0('This app is designed for clips of ', settings$max_clip_duration,' seconds or less. Producing longer spectrograms is very slow and best not attempted here.'),
type='error'
)
}
if(length(signal)/sr <= settings$max_clip_duration) {
#Produce the spectrogram
spec_fast(signal = signal,
sr = sr,
window_size = input$window_size,
overlap = input$overlap/100,
theme = input$speccolour,
ylim = input$y_range*1000)
}
}
})
#create the audio player
output$player <- renderUI({
file1 <- file.path( 'audio/', global$file_current )
tags$audio(src = file.path(file1), controls=NA,type='audio/wav')
})
#text for current clip
output$file_count <- renderText(
paste("Clip ", global$file_counter, " of ", global$n_files))
output$file_current <- renderText(
paste("File: ", global$file_current))
}