Skip to content

Commit c03e3e4

Browse files
bbpennelkrwong
andauthored
Merge M4A development into main (#1825)
* BXC-4630 derivative MP3 from WAV (#1789) * generate mp3 derivatives from wav files * generate mp3 derivatives from wav files * fix destroy derivative and enhancement tests * fix javadoc, unique startuporder value and route id, add log statements/test/assertions, use FcrepoJmsConstants, update context file * remove constructor, rename bean to addAudioAccessCopyProcessor, add test, update context files * rename bean addAudioAccessCopyProcessor in context file * add audio/wave and audio/x-wave to mimetypes * switch mp3 to m4a * add audioDerivativeProcessor to service-context * change audio temp path from -access to -audio * add audio mimetypes to pattern (#1798) * use m4a derivates for access * fix test * add audio access datastream to isAudio, setSoundContent, and IIIFv3ViewableFilter * soundContent conditional, add contentObject id to getAccessPath, fix audioDs resolution * use cover viewer if audio access datastream present * remove changes to FullRecordController, add audio datastream to hasViewableFiles and performQuery * fix test * add audio derivative to DerivativeServiceTest * Code climate --------- Co-authored-by: krwong <[email protected]> Co-authored-by: krwong <[email protected]>
1 parent 338b4c4 commit c03e3e4

File tree

24 files changed

+586
-17
lines changed

24 files changed

+586
-17
lines changed

auth-api/src/main/java/edu/unc/lib/boxc/auth/api/services/DatastreamPermissionUtil.java

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class DatastreamPermissionUtil {
2323
DS_PERMISSION_MAP = new EnumMap<>(DatastreamType.class);
2424
DS_PERMISSION_MAP.put(DatastreamType.FULLTEXT_EXTRACTION, Permission.viewHidden);
2525
DS_PERMISSION_MAP.put(DatastreamType.JP2_ACCESS_COPY, Permission.viewAccessCopies);
26+
DS_PERMISSION_MAP.put(DatastreamType.AUDIO_ACCESS_COPY, Permission.viewAccessCopies);
2627
DS_PERMISSION_MAP.put(DatastreamType.ACCESS_SURROGATE, Permission.viewAccessCopies);
2728
DS_PERMISSION_MAP.put(DatastreamType.MD_DESCRIPTIVE, Permission.viewMetadata);
2829
DS_PERMISSION_MAP.put(DatastreamType.MD_DESCRIPTIVE_HISTORY, Permission.viewHidden);

indexing-solr/src/test/java/edu/unc/lib/boxc/indexing/solr/filter/SetDatastreamFilterTest.java

+17
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import java.util.stream.Collectors;
4646

4747
import static edu.unc.lib.boxc.indexing.solr.test.MockRepositoryObjectHelpers.makeFileObject;
48+
import static edu.unc.lib.boxc.model.api.DatastreamType.AUDIO_ACCESS_COPY;
4849
import static edu.unc.lib.boxc.model.api.DatastreamType.JP2_ACCESS_COPY;
4950
import static edu.unc.lib.boxc.model.api.DatastreamType.ORIGINAL_FILE;
5051
import static edu.unc.lib.boxc.model.api.DatastreamType.TECHNICAL_METADATA;
@@ -118,6 +119,7 @@ public class SetDatastreamFilterTest {
118119
private static final long PREMIS_SIZE = 893l;
119120

120121
private static final long JP2_SIZE = 11;
122+
private static final long AUDIO_SIZE = 11;
121123

122124
private AutoCloseable closeable;
123125

@@ -255,6 +257,8 @@ public void fileObjectAudioOnlyBinaryTest() throws Exception {
255257
fileResource(TECHNICAL_METADATA.getId(), FILE2_SIZE, FILE2_MIMETYPE, FILE2_NAME, FILE2_DIGEST));
256258
when(binObj2.getBinaryStream()).thenReturn(getClass().getResourceAsStream("/datastream/techmd_mp3.xml"));
257259
when(fileObj.getBinaryObjects()).thenReturn(Arrays.asList(binObj, binObj2));
260+
List<Derivative> derivs = makeAudioDerivative();
261+
when(derivativeService.getDerivatives(pid)).thenReturn(derivs);
258262
dip.setContentObject(fileObj);
259263

260264
filter.filter(dip);
@@ -263,6 +267,8 @@ public void fileObjectAudioOnlyBinaryTest() throws Exception {
263267
FILE_MP3_SIZE, FILE_MP3_MIMETYPE, FILE_MP3_NAME, FILE_MP3_DIGEST, null, FILE_MP3_EXTENT);
264268
assertContainsDatastream(idb.getDatastream(), TECHNICAL_METADATA.getId(),
265269
FILE2_SIZE, FILE2_MIMETYPE, FILE2_NAME, FILE2_DIGEST, null, null);
270+
assertContainsDatastream(idb.getDatastream(), AUDIO_ACCESS_COPY.getId(),
271+
AUDIO_SIZE, AUDIO_ACCESS_COPY.getMimetype(), "access.m4a", null, null, null);
266272
}
267273

268274
@Test
@@ -297,6 +303,8 @@ public void fileObjectAudioOnlyBinaryWithDotMillisecondsSeperatorTest() throws E
297303
fileResource(TECHNICAL_METADATA.getId(), FILE2_SIZE, FILE2_MIMETYPE, FILE2_NAME, FILE2_DIGEST));
298304
when(binObj2.getBinaryStream()).thenReturn(getClass().getResourceAsStream("/datastream/techmd_dot_separated_milliseconds.xml"));
299305
when(fileObj.getBinaryObjects()).thenReturn(Arrays.asList(binObj, binObj2));
306+
List<Derivative> derivs = makeAudioDerivative();
307+
when(derivativeService.getDerivatives(pid)).thenReturn(derivs);
300308
dip.setContentObject(fileObj);
301309

302310
filter.filter(dip);
@@ -305,6 +313,8 @@ public void fileObjectAudioOnlyBinaryWithDotMillisecondsSeperatorTest() throws E
305313
FILE_MP3_SIZE, FILE_MP3_MIMETYPE, FILE_MP3_NAME, FILE_MP3_DIGEST, null, FILE_MP3_EXTENT);
306314
assertContainsDatastream(idb.getDatastream(), TECHNICAL_METADATA.getId(),
307315
FILE2_SIZE, FILE2_MIMETYPE, FILE2_NAME, FILE2_DIGEST, null, null);
316+
assertContainsDatastream(idb.getDatastream(), AUDIO_ACCESS_COPY.getId(),
317+
AUDIO_SIZE, AUDIO_ACCESS_COPY.getMimetype(), "access.m4a", null, null, null);
308318
}
309319

310320
@Test
@@ -768,4 +778,11 @@ private List<Derivative> makeJP2Derivative() throws IOException {
768778

769779
return List.of(new Derivative(JP2_ACCESS_COPY, jp2File));
770780
}
781+
782+
private List<Derivative> makeAudioDerivative() throws IOException {
783+
File m4aFile = derivDir.resolve("access.m4a").toFile();
784+
FileUtils.write(m4aFile, "m4a content", "UTF-8");
785+
786+
return List.of(new Derivative(AUDIO_ACCESS_COPY, m4aFile));
787+
}
771788
}

model-api/src/main/java/edu/unc/lib/boxc/model/api/DatastreamType.java

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414
public enum DatastreamType {
1515
ACCESS_SURROGATE("access_surrogate", "application/octet-stream", null, null, EXTERNAL),
16+
AUDIO_ACCESS_COPY("audio", "audio/aac", "m4a", null, EXTERNAL),
1617
FULLTEXT_EXTRACTION("fulltext", "text/plain", "txt", null, EXTERNAL),
1718
JP2_ACCESS_COPY("jp2", "image/jp2", "jp2", null, EXTERNAL),
1819
MD_DESCRIPTIVE("md_descriptive", "text/xml", "xml", METADATA_CONTAINER, INTERNAL),

model-fcrepo/src/test/java/edu/unc/lib/boxc/model/fcrepo/services/DerivativeServiceTest.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.nio.file.Paths;
1515
import java.util.List;
1616

17+
import static edu.unc.lib.boxc.model.api.DatastreamType.AUDIO_ACCESS_COPY;
1718
import static edu.unc.lib.boxc.model.api.DatastreamType.FULLTEXT_EXTRACTION;
1819
import static edu.unc.lib.boxc.model.api.DatastreamType.JP2_ACCESS_COPY;
1920
import static edu.unc.lib.boxc.model.api.DatastreamType.ORIGINAL_FILE;
@@ -73,18 +74,22 @@ public void testGetDerivativeNotExist() throws Exception {
7374
public void testGetDerivatives() throws Exception {
7475
File originalDerivFile1 = createDerivative(pid, FULLTEXT_EXTRACTION);
7576
File originalDerivFil21 = createDerivative(pid, JP2_ACCESS_COPY);
77+
File originalDerivFile3 = createDerivative(pid, AUDIO_ACCESS_COPY);
7678

7779
List<Derivative> derivs = derivativeService.getDerivatives(pid);
78-
assertEquals(2, derivs.size());
80+
assertEquals(3, derivs.size());
7981

8082
Derivative textDeriv = findDerivative(derivs, FULLTEXT_EXTRACTION);
8183
Derivative jp2Deriv = findDerivative(derivs, JP2_ACCESS_COPY);
84+
Derivative audioDeriv = findDerivative(derivs, AUDIO_ACCESS_COPY);
8285

8386
assertNotNull(textDeriv);
8487
assertNotNull(jp2Deriv);
88+
assertNotNull(audioDeriv);
8589

8690
assertEquals(originalDerivFile1, textDeriv.getFile());
8791
assertEquals(originalDerivFil21, jp2Deriv.getFile());
92+
assertEquals(originalDerivFile3, audioDeriv.getFile());
8893
}
8994

9095
@Test

search-solr/src/main/java/edu/unc/lib/boxc/search/solr/filters/IIIFv3ViewableFilter.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ public String toFilterString() {
2323
var fileTypeFilter = getFileTypes().stream().map( type -> fileTypeField + ":" + type)
2424
.collect(Collectors.joining(" OR ", "(", ")"));
2525
var datastreamFilter = SearchFieldKey.DATASTREAM.getSolrField() + ":" + DatastreamType.JP2_ACCESS_COPY.getId() + "|*";
26-
return "(" + fileTypeFilter + ") OR (" + datastreamFilter + ")";
26+
var datastreamFilterAudio = SearchFieldKey.DATASTREAM.getSolrField() + ":" + DatastreamType.AUDIO_ACCESS_COPY.getId() + "|*";
27+
return "(" + fileTypeFilter + ") OR (" + datastreamFilter + ") OR (" + datastreamFilterAudio + ")";
2728
}
2829

2930
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package edu.unc.lib.boxc.services.camel.audio;
2+
3+
import edu.unc.lib.boxc.services.camel.util.CdrFcrepoHeaders;
4+
import org.apache.camel.Exchange;
5+
import org.apache.camel.Message;
6+
import org.apache.camel.Processor;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
import java.util.regex.Pattern;
11+
12+
/**
13+
* Processor which validates and prepares audio objects for producing derivatives
14+
* @author krwong
15+
*/
16+
public class AudioDerivativeProcessor implements Processor {
17+
private static final Logger log = LoggerFactory.getLogger(AudioDerivativeProcessor.class);
18+
19+
private static final Pattern MIMETYPE_PATTERN =
20+
Pattern.compile("^(audio.(basic|mpeg|mp4|x-aiff|x-ms-wma|x-wave|x-wav|wav|wave|3gpp))$");
21+
22+
/**
23+
* Returns true if the subject of the exchange is a binary which
24+
* is eligible for having audio derivatives generated from it.
25+
* @param exchange
26+
* @return
27+
*/
28+
public static boolean allowedAudioType(Exchange exchange) {
29+
Message in = exchange.getIn();
30+
String mimetype = (String) in.getHeader(CdrFcrepoHeaders.CdrBinaryMimeType);
31+
String binPath = (String) in.getHeader(CdrFcrepoHeaders.CdrBinaryPath);
32+
33+
if (!MIMETYPE_PATTERN.matcher(mimetype).matches()) {
34+
log.debug("File type {} on object {} is not applicable for audio derivatives", mimetype, binPath);
35+
return false;
36+
}
37+
38+
log.debug("Object {} with type {} is permitted for audio derivatives", binPath, mimetype);
39+
return true;
40+
}
41+
42+
@Override
43+
public void process(Exchange exchange) throws Exception {
44+
Message in = exchange.getIn();
45+
String mimetype = (String) in.getHeader(CdrFcrepoHeaders.CdrBinaryMimeType);
46+
47+
String binPath = (String) in.getHeader(CdrFcrepoHeaders.CdrBinaryPath);
48+
log.debug("Keeping existing audio path as {} for type {}", binPath, mimetype);
49+
in.setHeader(CdrFcrepoHeaders.CdrAudioPath, binPath);
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package edu.unc.lib.boxc.services.camel.audio;
2+
3+
import edu.unc.lib.boxc.model.api.exceptions.RepositoryException;
4+
import edu.unc.lib.boxc.services.camel.images.AddDerivativeProcessor;
5+
import edu.unc.lib.boxc.services.camel.util.CdrFcrepoHeaders;
6+
import org.apache.camel.BeanInject;
7+
import org.apache.camel.LoggingLevel;
8+
import org.apache.camel.builder.RouteBuilder;
9+
import org.apache.camel.spi.UuidGenerator;
10+
import org.apache.camel.support.DefaultUuidGenerator;
11+
import org.slf4j.Logger;
12+
13+
import static org.slf4j.LoggerFactory.getLogger;
14+
15+
/**
16+
* Router which triggers the creation of audio derivatives
17+
* @author krwong
18+
*/
19+
public class AudioEnhancementsRouter extends RouteBuilder {
20+
private static final Logger log = getLogger(AudioEnhancementsRouter.class);
21+
22+
@BeanInject(value = "addAudioAccessCopyProcessor")
23+
private AddDerivativeProcessor addAudioAccessCopyProcessor;
24+
25+
private UuidGenerator uuidGenerator;
26+
27+
/**
28+
* Configure the audio enhancement route workflow.
29+
*/
30+
@Override
31+
public void configure() throws Exception {
32+
AudioDerivativeProcessor audioDerivProcessor = new AudioDerivativeProcessor();
33+
34+
uuidGenerator = new DefaultUuidGenerator();
35+
36+
onException(RepositoryException.class)
37+
.redeliveryDelay("{{error.retryDelay}}")
38+
.maximumRedeliveries("{{error.maxRedeliveries}}")
39+
.backOffMultiplier("{{error.backOffMultiplier}}")
40+
.retryAttemptedLogLevel(LoggingLevel.WARN);
41+
42+
from("direct:process.enhancement.audioAccessCopy")
43+
.routeId("AudioAccessCopy")
44+
.startupOrder(25)
45+
.log(LoggingLevel.DEBUG, log, "Access copy triggered")
46+
.filter().method(addAudioAccessCopyProcessor, "needsRun")
47+
.filter().method(audioDerivProcessor, "allowedAudioType")
48+
.bean(audioDerivProcessor)
49+
.log(LoggingLevel.INFO, log, "Creating/Updating AAC access copy for ${headers[CdrAudioPath]}")
50+
// Generate an random identifier to avoid derivative collisions
51+
.setBody(exchange -> uuidGenerator.generateUuid())
52+
.setHeader(CdrFcrepoHeaders.CdrTempPath, simple("${properties:services.tempDirectory}/${body}-audio"))
53+
.doTry()
54+
.recipientList(simple("exec:/bin/sh?args=${properties:cdr.enhancement.bin}/convertWav.sh "
55+
+ "${headers[CdrAudioPath]} ${headers[CdrTempPath]}"))
56+
.bean(addAudioAccessCopyProcessor)
57+
.endDoTry()
58+
.doFinally()
59+
.bean(addAudioAccessCopyProcessor, "cleanupTempFile")
60+
.end();
61+
}
62+
}

services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/destroyDerivatives/DestroyDerivativesRouter.java

+17
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import edu.unc.lib.boxc.services.camel.fulltext.FulltextProcessor;
44
import edu.unc.lib.boxc.services.camel.images.ImageDerivativeProcessor;
5+
import edu.unc.lib.boxc.services.camel.audio.AudioDerivativeProcessor;
6+
57
import org.apache.camel.BeanInject;
68
import org.apache.camel.LoggingLevel;
79
import org.apache.camel.PropertyInject;
@@ -28,6 +30,9 @@ public class DestroyDerivativesRouter extends RouteBuilder {
2830
@BeanInject(value = "destroyFulltextProcessor")
2931
private DestroyDerivativesProcessor destroyFulltextProcessor;
3032

33+
@BeanInject(value = "destroyAudioProcessor")
34+
private DestroyDerivativesProcessor destroyAudioProcessor;
35+
3136
private String destroyDerivativesStreamCamel;
3237
private long errorRetryDelay;
3338
private int errorMaxRedeliveries;
@@ -51,6 +56,8 @@ public void configure() throws Exception {
5156
.to("direct:image.derivatives.destroy")
5257
.when(method(FulltextProcessor.class, "allowedTextType"))
5358
.to("direct:fulltext.derivatives.destroy")
59+
.when(method(AudioDerivativeProcessor.class, "allowedAudioType"))
60+
.to("direct:audio.derivatives.destroy")
5461
.end();
5562

5663
from("direct:fulltext.derivatives.destroy")
@@ -64,6 +71,12 @@ public void configure() throws Exception {
6471
.startupOrder(202)
6572
.log(LoggingLevel.DEBUG, log, "Destroying access copy derivatives")
6673
.bean(destroyAccessCopyProcessor);
74+
75+
from("direct:audio.derivatives.destroy")
76+
.routeId("CdrDestroyAudio")
77+
.startupOrder(199)
78+
.log(LoggingLevel.DEBUG, log, "Destroying derivative audio files")
79+
.bean(destroyAudioProcessor);
6780
}
6881

6982
public void setDestroyedMsgProcessor(DestroyedMsgProcessor destroyedMsgProcessor) {
@@ -78,6 +91,10 @@ public void setDestroyFulltextProcessor(DestroyDerivativesProcessor destroyFullt
7891
this.destroyFulltextProcessor = destroyFulltextProcessor;
7992
}
8093

94+
public void setDestroyAudioProcessor(DestroyDerivativesProcessor destroyAudioProcessor) {
95+
this.destroyAudioProcessor = destroyAudioProcessor;
96+
}
97+
8198
@PropertyInject("cdr.destroy.derivatives.stream.camel")
8299
public void setDestroyDerivativesStreamCamel(String destroyDerivativesStreamCamel) {
83100
this.destroyDerivativesStreamCamel = destroyDerivativesStreamCamel;

services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/enhancements/EnhancementRouter.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public class EnhancementRouter extends RouteBuilder {
3737
@PropertyInject(value = "cdr.enhancement.processingThreads")
3838
private Integer enhancementThreads;
3939

40-
private static final String DEFAULT_ENHANCEMENTS = "imageAccessCopy,extractFulltext";
40+
private static final String DEFAULT_ENHANCEMENTS = "imageAccessCopy,extractFulltext,audioAccessCopy";
4141
@Override
4242
public void configure() throws Exception {
4343

services-camel-app/src/main/java/edu/unc/lib/boxc/services/camel/util/CdrFcrepoHeaders.java

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ public abstract class CdrFcrepoHeaders {
2222
// URI identifying the location
2323
public static final String CdrImagePath = "CdrImagePath";
2424

25+
// URI identifying the location
26+
public static final String CdrAudioPath = "CdrAudioPath";
27+
2528
// File path for a temp file
2629
public static final String CdrTempPath = "CdrTempPath";
2730

services-camel-app/src/main/webapp/WEB-INF/service-context.xml

+14
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,14 @@
223223
<constructor-arg value="${cdr.enhancement.path.fulltext}" />
224224
</bean>
225225

226+
<bean id="addAudioAccessCopyProcessor" class="edu.unc.lib.boxc.services.camel.images.AddDerivativeProcessor">
227+
<constructor-arg value="#{T(edu.unc.lib.boxc.model.api.DatastreamType).AUDIO_ACCESS_COPY.getExtension()}" />
228+
<constructor-arg value="${cdr.enhancement.path.audio}" />
229+
</bean>
230+
231+
<bean id="audioAccessCopyProcessor" class="edu.unc.lib.boxc.services.camel.audio.AudioDerivativeProcessor">
232+
</bean>
233+
226234
<bean id="destroyedMsgProcessor" class="edu.unc.lib.boxc.services.camel.destroyDerivatives.DestroyedMsgProcessor">
227235
<property name="jp2BasePath" value="${cdr.enhancement.path.jp2}" />
228236
</bean>
@@ -241,6 +249,11 @@
241249
<constructor-arg value="#{T(edu.unc.lib.boxc.model.api.DatastreamType).FULLTEXT_EXTRACTION.getExtension()}" />
242250
<constructor-arg value="${cdr.enhancement.path.fulltext}" />
243251
</bean>
252+
253+
<bean id="destroyAudioProcessor" class="edu.unc.lib.boxc.services.camel.destroyDerivatives.DestroyDerivativesProcessor">
254+
<constructor-arg value="#{T(edu.unc.lib.boxc.model.api.DatastreamType).AUDIO_ACCESS_COPY.getExtension()}" />
255+
<constructor-arg value="${cdr.enhancement.path.audio}" />
256+
</bean>
244257

245258
<bean id="indexingMessageProcessor" class="edu.unc.lib.boxc.services.camel.triplesReindexing.IndexingMessageProcessor">
246259
</bean>
@@ -547,6 +560,7 @@
547560
<camel:package>edu.unc.lib.boxc.services.camel.enhancements</camel:package>
548561
<camel:package>edu.unc.lib.boxc.services.camel.images</camel:package>
549562
<camel:package>edu.unc.lib.boxc.services.camel.fulltext</camel:package>
563+
<camel:package>edu.unc.lib.boxc.services.camel.audio</camel:package>
550564
<camel:package>edu.unc.lib.boxc.services.camel.solr</camel:package>
551565
<camel:package>edu.unc.lib.boxc.services.camel.binaryCleanup</camel:package>
552566
<!-- Initialize metaServicesRouter after the routes it depends on -->

0 commit comments

Comments
 (0)