Added added service for image formats not supported by ImageIO (jp2)

This commit is contained in:
Michael Spalti
2021-12-10 17:14:17 -08:00
parent a009a24474
commit e07f562469
11 changed files with 230 additions and 29 deletions

View File

@@ -8,18 +8,30 @@
package org.dspace.app.canvasdimension; package org.dspace.app.canvasdimension;
import java.util.Arrays; import java.util.Arrays;
import java.util.UUID;
import org.apache.commons.cli.*; import org.apache.commons.cli.*;
import org.dspace.app.canvasdimension.factory.IIIFCanvasDimensionServiceFactory;
import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService;
import org.dspace.content.Collection; import org.dspace.content.Collection;
import org.dspace.content.Community; import org.dspace.content.Community;
import org.dspace.content.DSpaceObject; import org.dspace.content.DSpaceObject;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.core.Constants; import org.dspace.core.Constants;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.EPersonService;
import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.factory.HandleServiceFactory;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
public class CanvasDimensionCLI { public class CanvasDimensionCLI {
private static final EPersonService epersonService = EPersonServiceFactory.getInstance().getEPersonService();
private static final ConfigurationService configurationService = DSpaceServicesFactory.getInstance()
.getConfigurationService();
private CanvasDimensionCLI() {} private CanvasDimensionCLI() {}
public static void main(String[] argv) throws Exception { public static void main(String[] argv) throws Exception {
@@ -27,16 +39,26 @@ public class CanvasDimensionCLI {
boolean force = false; boolean force = false;
boolean isQuiet = false; boolean isQuiet = false;
String identifier = null; String identifier = null;
String eperson = null;
int max2Process = Integer.MAX_VALUE; int max2Process = Integer.MAX_VALUE;
boolean iiifEnabled = configurationService.getBooleanProperty("iiif.enabled");
if (!iiifEnabled) {
System.out.println("IIIF is not enabled on this DSpace server.");
return;
}
Context context = new Context(); Context context = new Context();
IIIFCanvasDimensionProcessor canvasProcessor = new IIIFCanvasDimensionProcessor(); IIIFCanvasDimensionService canvasProcessor = IIIFCanvasDimensionServiceFactory.getInstance()
.getIiifCanvasDimensionService();
CommandLineParser parser = new DefaultParser(); CommandLineParser parser = new DefaultParser();
Options options = new Options(); Options options = new Options();
options.addOption("i", "identifier", true, options.addOption("i", "identifier", true,
"process IIIF canvas dimensions for images belonging to identifier"); "process IIIF canvas dimensions for images belonging to identifier");
options.addOption("e", "eperson", true,
"email of eperson setting canvas dimensions");
options.addOption("f", "force", false, options.addOption("f", "force", false,
"force update of all IIIF canvas height and width dimensions"); "force update of all IIIF canvas height and width dimensions");
options.addOption("q", "quiet", false, options.addOption("q", "quiet", false,
@@ -74,6 +96,9 @@ public class CanvasDimensionCLI {
if (line.hasOption('q')) { if (line.hasOption('q')) {
isQuiet = true; isQuiet = true;
} }
if (line.hasOption('e')) {
eperson = line.getOptionValue('e');
}
if (line.hasOption('i')) { if (line.hasOption('i')) {
identifier = line.getOptionValue('i'); identifier = line.getOptionValue('i');
} else { } else {
@@ -111,6 +136,21 @@ public class CanvasDimensionCLI {
+ identifier + " to a DSpace object"); + identifier + " to a DSpace object");
} }
EPerson user;
if (eperson.indexOf('@') != -1) {
// @ sign, must be an email
user = epersonService.findByEmail(context, eperson);
} else {
user = epersonService.find(context, UUID.fromString(eperson));
}
if (user == null) {
System.out.println("Error, eperson cannot be found: " + eperson);
System.exit(1);
}
context.setCurrentUser(user);
canvasProcessor.setForceProcessing(force); canvasProcessor.setForceProcessing(force);
canvasProcessor.setMax2Process(max2Process); canvasProcessor.setMax2Process(max2Process);

View File

@@ -0,0 +1,65 @@
package org.dspace.app.canvasdimension;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dspace.app.canvasdimension.service.IIIFApiQueryService;
import org.dspace.content.Bitstream;
import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
public class IIIFApiQueryServiceImpl implements IIIFApiQueryService, InitializingBean {
@Autowired(required = true)
protected ConfigurationService configurationService;
String iiifImageServer;
@Override
public void afterPropertiesSet() throws Exception {
iiifImageServer = configurationService.getProperty("iiif.image.server");
}
public int[] getImageDimensions(Bitstream bitstream) {
return getIiifImageDimensions(bitstream);
}
/**
* Retrieves image dimensions from the image server (IIIF Image API v.2.1.1).
* @param bitstream the bitstream DSO
* @return image dimensions
*/
private int[] getIiifImageDimensions(Bitstream bitstream) {
int[] arr = new int[2];
String path = iiifImageServer + bitstream.getID() + "/info.json";
URL url;
try {
url = new URL(path);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
JsonNode parent = new ObjectMapper().readTree(response.toString());
arr[0] = parent.get("width").asInt();
arr[1] = parent.get("height").asInt();
return arr;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

View File

@@ -16,11 +16,14 @@ import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.dspace.app.canvasdimension.service.IIIFApiQueryService;
import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService;
import org.dspace.content.Bitstream; import org.dspace.content.Bitstream;
import org.dspace.content.Bundle; import org.dspace.content.Bundle;
import org.dspace.content.Collection; import org.dspace.content.Collection;
import org.dspace.content.Community; import org.dspace.content.Community;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
import org.dspace.content.service.BitstreamService; import org.dspace.content.service.BitstreamService;
import org.dspace.content.service.CommunityService; import org.dspace.content.service.CommunityService;
import org.dspace.content.service.DSpaceObjectService; import org.dspace.content.service.DSpaceObjectService;
@@ -28,19 +31,20 @@ import org.dspace.content.service.ItemService;
import org.dspace.core.Constants; import org.dspace.core.Constants;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.license.CreativeCommonsServiceImpl; import org.dspace.license.CreativeCommonsServiceImpl;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
public class IIIFCanvasDimensionProcessor implements InitializingBean { public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionService {
@Autowired @Autowired(required = true)
ItemService itemService; ItemService itemService;
@Autowired @Autowired(required = true)
CommunityService communityService; CommunityService communityService;
@Autowired @Autowired(required = true)
BitstreamService bitstreamService; BitstreamService bitstreamService;
@Autowired @Autowired(required = true)
DSpaceObjectService<Bitstream> dSpaceObjectService; DSpaceObjectService<Bitstream> dSpaceObjectService;
@Autowired(required = true)
IIIFApiQueryService iiifApiQuery;
// metadata used to enable the iiif features on the item // metadata used to enable the iiif features on the item
public static final String METADATA_IIIF_ENABLED = "dspace.iiif.enabled"; public static final String METADATA_IIIF_ENABLED = "dspace.iiif.enabled";
@@ -55,20 +59,17 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean {
private int processed = 0; private int processed = 0;
protected Item currentItem = null; // TODO needed? protected Item currentItem = null; // TODO needed?
@Override
public void afterPropertiesSet() throws Exception {
}
/** /**
* Set the force processing property. If true, existing canvas * Set the force processing property. If true, existing canvas
* metadata will be replaced. * metadata will be replaced.
* @param force * @param force
*/ */
@Override
public void setForceProcessing(boolean force) { public void setForceProcessing(boolean force) {
forceProcessing = force; forceProcessing = force;
} }
@Override
public void setIsQuiet(boolean quiet) { public void setIsQuiet(boolean quiet) {
isQuiet = quiet; isQuiet = quiet;
} }
@@ -77,6 +78,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean {
* Set the maximum number of items to process. * Set the maximum number of items to process.
* @param max2Process * @param max2Process
*/ */
@Override
public void setMax2Process(int max2Process) { public void setMax2Process(int max2Process) {
this.max2Process = max2Process; this.max2Process = max2Process;
} }
@@ -85,6 +87,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean {
* Set dso identifiers to skip. * Set dso identifiers to skip.
* @param skipList * @param skipList
*/ */
@Override
public void setSkipList(List<String> skipList) { public void setSkipList(List<String> skipList) {
this.skipList = skipList; this.skipList = skipList;
} }
@@ -94,6 +97,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean {
* @param context * @param context
* @throws Exception * @throws Exception
*/ */
@Override
public void processSite(Context context) throws Exception { public void processSite(Context context) throws Exception {
if (skipList != null) { if (skipList != null) {
//if a skip-list exists, we need to filter community-by-community //if a skip-list exists, we need to filter community-by-community
@@ -119,6 +123,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean {
* @param community * @param community
* @throws Exception * @throws Exception
*/ */
@Override
public void processCommunity(Context context, Community community) throws Exception { public void processCommunity(Context context, Community community) throws Exception {
if (!inSkipList(community.getHandle())) { if (!inSkipList(community.getHandle())) {
List<Community> subcommunities = community.getSubcommunities(); List<Community> subcommunities = community.getSubcommunities();
@@ -138,6 +143,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean {
* @param collection * @param collection
* @throws Exception * @throws Exception
*/ */
@Override
public void processCollection(Context context, Collection collection) throws Exception { public void processCollection(Context context, Collection collection) throws Exception {
if (!inSkipList(collection.getHandle())) { if (!inSkipList(collection.getHandle())) {
Iterator<Item> itemIterator = itemService.findAllByCollection(context, collection); Iterator<Item> itemIterator = itemService.findAllByCollection(context, collection);
@@ -153,6 +159,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean {
* @param item * @param item
* @throws Exception * @throws Exception
*/ */
@Override
public void processItem(Context context, Item item) throws Exception { public void processItem(Context context, Item item) throws Exception {
if (!inSkipList(item.getHandle())) { if (!inSkipList(item.getHandle())) {
boolean isIIIFItem = item.getMetadata().stream().filter(m -> m.getMetadataField().toString('.') boolean isIIIFItem = item.getMetadata().stream().filter(m -> m.getMetadataField().toString('.')
@@ -160,8 +167,10 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean {
.anyMatch(m -> m.getValue().equalsIgnoreCase("true") || .anyMatch(m -> m.getValue().equalsIgnoreCase("true") ||
m.getValue().equalsIgnoreCase("yes")); m.getValue().equalsIgnoreCase("yes"));
if (isIIIFItem) { if (isIIIFItem) {
if (processBundles(context, item)) { if (processItemBundles(context, item)) {
++processed; ++processed;
// commit changes
context.commit();
} }
} }
} }
@@ -174,15 +183,16 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean {
* @return * @return
* @throws Exception * @throws Exception
*/ */
private boolean processBundles(Context context, Item item) throws Exception { private boolean processItemBundles(Context context, Item item) throws Exception {
List<Bundle> bundles = getIIIFBundles(item); List<Bundle> bundles = getIIIFBundles(item);
boolean done = false; boolean done = false;
for (Bundle bundle : bundles) { for (Bundle bundle : bundles) {
List<Bitstream> myBitstreams = bundle.getBitstreams(); List<Bitstream> bitstreams = bundle.getBitstreams();
for (Bitstream myBitstream : myBitstreams) { for (Bitstream bit : bitstreams) {
done |= processBitstream(context, myBitstream); done |= processBitstream(context, bit);
} }
} }
itemService.update(context, item);
return done; return done;
} }
@@ -197,15 +207,23 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean {
*/ */
private boolean processBitstream(Context context, Bitstream bitstream) throws Exception { private boolean processBitstream(Context context, Bitstream bitstream) throws Exception {
boolean processed = false; boolean processed = false;
boolean isUnsupported = bitstream.getFormat(context).getMIMEType().contains("image/jp2");
boolean isImage = bitstream.getFormat(context).getMIMEType().contains("image/"); boolean isImage = bitstream.getFormat(context).getMIMEType().contains("image/");
if (isImage) { if (isImage) {
Optional op = bitstream.getMetadata().stream().filter(m -> m.getMetadataField().toString('.') Optional<MetadataValue> op = bitstream.getMetadata().stream()
.contentEquals(IIIF_WIDTH_METADATA)).findFirst(); .filter(m -> m.getMetadataField().toString('.')
.contentEquals(IIIF_WIDTH_METADATA)).findFirst();
if (op.isEmpty() || forceProcessing) { if (op.isEmpty() || forceProcessing) {
InputStream srcStream = bitstreamService.retrieve(context, bitstream); int[] dims;
int[] dims = ImageDimensionReader.getImageDimensions(srcStream); if (isUnsupported) {
dims = iiifApiQuery.getImageDimensions(bitstream);
} else {
InputStream stream = bitstreamService.retrieve(context, bitstream);
dims = ImageDimensionReader.getImageDimensions(stream);
}
if (dims != null) { if (dims != null) {
processed = setBitstreamMetadata(context, bitstream, dims); processed = setBitstreamMetadata(context, bitstream, dims);
bitstreamService.update(context, bitstream);
} }
} }
} }
@@ -216,14 +234,12 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean {
try { try {
dSpaceObjectService.clearMetadata(context, bitstream, "iiif", dSpaceObjectService.clearMetadata(context, bitstream, "iiif",
"image", "width", Item.ANY); "image", "width", Item.ANY);
dSpaceObjectService.addAndShiftRightMetadata(context, bitstream, "iiif", dSpaceObjectService.setMetadataSingleValue(context, bitstream, "iiif",
"image", "width", null, "image", "width", Item.ANY, String.valueOf(dims[0]));
String.valueOf(dims[0]), null, -1, -1);
dSpaceObjectService.clearMetadata(context, bitstream, "iiif", dSpaceObjectService.clearMetadata(context, bitstream, "iiif",
"image", "height", Item.ANY); "image", "height", Item.ANY);
dSpaceObjectService.addAndShiftRightMetadata(context, bitstream, "iiif", dSpaceObjectService.setMetadataSingleValue(context, bitstream, "iiif",
"image", "height", null, "image", "height", Item.ANY, String.valueOf(dims[1]));
String.valueOf(dims[1]), null, -1, -1);
return true; return true;
} catch (SQLException e) { } catch (SQLException e) {
System.out.println("Unable to update metadata: " + e.getMessage()); System.out.println("Unable to update metadata: " + e.getMessage());

View File

@@ -18,8 +18,8 @@ public class ImageDimensionReader {
public static int[] getImageDimensions(InputStream image) throws Exception { public static int[] getImageDimensions(InputStream image) throws Exception {
int[] dims = new int[2]; int[] dims = new int[2];
BufferedImage buf = ImageIO.read(image); BufferedImage buf = ImageIO.read(image);
int width = buf.getWidth(); int width = buf.getWidth(null);
int height = buf.getHeight(); int height = buf.getHeight(null);
if (width > 0 && height > 0) { if (width > 0 && height > 0) {
dims[0] = width; dims[0] = width;
dims[1] = height; dims[1] = height;

View File

@@ -0,0 +1,15 @@
package org.dspace.app.canvasdimension.factory;
import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService;
import org.dspace.services.factory.DSpaceServicesFactory;
public abstract class IIIFCanvasDimensionServiceFactory {
public static IIIFCanvasDimensionServiceFactory getInstance() {
return DSpaceServicesFactory.getInstance().getServiceManager()
.getServiceByName("iiifCanvasDimensionServiceFactory",
IIIFCanvasDimensionServiceFactory.class);
}
public abstract IIIFCanvasDimensionService getIiifCanvasDimensionService();
}

View File

@@ -0,0 +1,15 @@
package org.dspace.app.canvasdimension.factory;
import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService;
import org.springframework.beans.factory.annotation.Autowired;
public class IIIFCanvasDimensionServiceFactoryImpl extends IIIFCanvasDimensionServiceFactory {
@Autowired(required = true)
private IIIFCanvasDimensionService iiifCanvasDimensionService;
@Override
public IIIFCanvasDimensionService getIiifCanvasDimensionService() {
return iiifCanvasDimensionService;
}
}

View File

@@ -0,0 +1,9 @@
package org.dspace.app.canvasdimension.service;
import org.dspace.content.Bitstream;
public interface IIIFApiQueryService {
public int[] getImageDimensions(Bitstream bitstream);
}

View File

@@ -0,0 +1,30 @@
package org.dspace.app.canvasdimension.service;
import java.util.List;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.core.Context;
public interface IIIFCanvasDimensionService {
public void processSite(Context context) throws Exception;
public void processCommunity(Context context, Community community) throws Exception;
public void processCollection(Context context, Collection collection) throws Exception;
public void processItem(Context context, Item item) throws Exception;
public void setForceProcessing(boolean force);
public void setIsQuiet(boolean quiet);
public void setMax2Process(int max2Process);
public void setSkipList(List<String> skipList);
}

View File

@@ -366,4 +366,11 @@
<class>org.dspace.statistics.AnonymizeStatistics</class> <class>org.dspace.statistics.AnonymizeStatistics</class>
</step> </step>
</command> </command>
<command>
<name>canvas-dimensions</name>
<description>Add canvas width and height metadata for IIIF enabled items.</description>
<step>
<class>org.dspace.app.canvasdimension.CanvasDimensionCLI</class>
</step>
</command>
</commands> </commands>

View File

@@ -10,6 +10,7 @@
<bean id="requestItemServiceFactory" class="org.dspace.app.requestitem.factory.RequestItemServiceFactoryImpl"/> <bean id="requestItemServiceFactory" class="org.dspace.app.requestitem.factory.RequestItemServiceFactoryImpl"/>
<bean id="itemExportServiceFactory" class="org.dspace.app.itemexport.factory.ItemExportServiceFactoryImpl"/> <bean id="itemExportServiceFactory" class="org.dspace.app.itemexport.factory.ItemExportServiceFactoryImpl"/>
<bean id="itemImportServiceFactory" class="org.dspace.app.itemimport.factory.ItemImportServiceFactoryImpl"/> <bean id="itemImportServiceFactory" class="org.dspace.app.itemimport.factory.ItemImportServiceFactoryImpl"/>
<bean id="iiifCanvasDimensionServiceFactory" class="org.dspace.app.canvasdimension.factory.IIIFCanvasDimensionServiceFactoryImpl"/>
<bean id="mediaFilterServiceFactory" class="org.dspace.app.mediafilter.factory.MediaFilterServiceFactoryImpl"/> <bean id="mediaFilterServiceFactory" class="org.dspace.app.mediafilter.factory.MediaFilterServiceFactoryImpl"/>
<bean id="sfxServiceFactory" class="org.dspace.app.sfx.factory.SfxServiceFactoryImpl"/> <bean id="sfxServiceFactory" class="org.dspace.app.sfx.factory.SfxServiceFactoryImpl"/>
<bean id="appUtilServiceFactory" class="org.dspace.app.util.factory.UtilServiceFactoryImpl"/> <bean id="appUtilServiceFactory" class="org.dspace.app.util.factory.UtilServiceFactoryImpl"/>

View File

@@ -17,6 +17,9 @@
<!--Ensure that bean remains prototype ! --> <!--Ensure that bean remains prototype ! -->
<bean class="org.dspace.app.mediafilter.MediaFilterServiceImpl" scope="prototype"/> <bean class="org.dspace.app.mediafilter.MediaFilterServiceImpl" scope="prototype"/>
<bean class="org.dspace.app.canvasdimension.IIIFCanvasDimensionServiceImpl" scope="prototype"/>
<bean class="org.dspace.app.canvasdimension.IIIFApiQueryServiceImpl" scope="prototype"/>
<bean class="org.dspace.app.sfx.SFXFileReaderServiceImpl" scope="prototype"/> <bean class="org.dspace.app.sfx.SFXFileReaderServiceImpl" scope="prototype"/>
<bean class="org.dspace.app.util.MetadataExposureServiceImpl"/> <bean class="org.dspace.app.util.MetadataExposureServiceImpl"/>