diff --git a/install/cope/README.md b/install/cope/README.md new file mode 100644 index 0000000..c4abb5d --- /dev/null +++ b/install/cope/README.md @@ -0,0 +1,50 @@ + +# COPE scripts + +## EIP extraction + +The script `eip_prepare_for_cope.py` extracts EIP files in a source directory to a target directory. It renames a specific subdirectory (`CaptureOne/Settings153` to `CaptureOne/Settings131`) and moves the EIP files into the `_raw` image directory. + +## Usage + +``` +usage: eip_prepare_for_cope.py [-h] -s SOURCE -t TARGET + +options: + -h, --help show this help message and exit + -s SOURCE, --source SOURCE + Source directory + -t TARGET, --target TARGET + Target directory +``` + +in Goobi workflow this can be integrated in a script step like this: + +```shell +/opt/digiverso/goobi/scripts/eip_prepare_for_cope.py -s {origpath} -t {origpath} +``` + +## COPE conversion + +The script `cope_folder.py` converts IIQ files from the source directory and uses COPE to convert them to tiff files located in the target directory. Optionally the given resolution value will be used. + +## Usage + +``` +usage: cope_folder.py [-h] -s SOURCE -t TARGET [-r RESOLUTION] + +options: + -h, --help show this help message and exit + -s SOURCE, --source SOURCE + -t TARGET, --target TARGET + -r RESOLUTION, --resolution RESOLUTION +``` + +The path to the cope binary can be given by an environment variable named `COPE_PATH`. + +In Goobi workflow a script step in the external queue can be used to run the script on a Windows workernode like this: + +```CMD +C:\Windows\py.exe D:\intranda\cope_folder.py -s "//mediaSMB-isilonArchive/GoobiMetadata/{s3_origpath}" -t "//mediaSMB-isilonArchive/GoobiMetadata/{s3_origpath}" -r "{process.File Resolution}" +``` + diff --git a/install/cope/cope_folder.py b/install/cope/cope_folder.py new file mode 100755 index 0000000..d9c72d7 --- /dev/null +++ b/install/cope/cope_folder.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 + +import logging +import os +import shutil +import subprocess +from argparse import ArgumentParser +from pathlib import Path + +loglevel = (os.environ.get('PYTHON_LOGLEVEL') or "DEBUG").upper() +numeric_level = getattr(logging, loglevel.upper(), None) +logging.basicConfig( + format='%(asctime)s %(levelname)-8s %(message)s', level=numeric_level) + +file_name_pattern = "*.IIQ" +path_cope = (os.environ.get('COPE_PATH') or Path( + "C:/cope.Win.13.1.15-name_cope131/COPE/Bin/COPE.exe")) + +cope_options = ["-bits=8"] + + +def find_files(root_dir: Path, file_name_pattern: str) -> list[list]: + """find files matching file_name_pattern in direct subdirectories of root_dir + + Args: + root_dir (Path): top level directory to perform search in + file_name_pattern (str): globbing expression to match for + + Returns: + list[list]: a list of lists with the filename and the parent directory basename of matching files + """ + found_files = [] + for file in Path(root_dir).glob("*/"+file_name_pattern): + found_files.append([file, file.parent.stem]) + return (found_files) + + +def get_parser(): + """provides argument parser""" + parser = ArgumentParser() + parser.add_argument("-s", "--source", dest="source", + required=True, type=str) + parser.add_argument("-t", "--target", dest="target", + required=True, type=str) + parser.add_argument("-r", "--resolution", dest="resolution", + required=False, type=int) + return parser + + +if __name__ == "__main__": + parser = get_parser() + args = parser.parse_args() + + sourcedir = Path(args.source) + targetdir = Path(args.target) + resolution = args.resolution + + logging.debug(f"path to cope: {path_cope}") + logging.debug(f"source directory: {sourcedir}") + logging.debug(f"target directory: {targetdir}") + + if resolution: + logging.debug(f"resolution: {resolution}") + cope_options.append(f"-resolution={resolution}") + + if not os.path.isdir(sourcedir): + logging.error("source directory does not exist") + exit(1) + + if not os.path.isdir(targetdir): + os.mkdir(targetdir) + logging.info("target directory created, as it did not exist") + + logging.info("start processing...") + files = find_files(sourcedir, file_name_pattern) + + if not files: + logging.info("no matching files found in source directory") + + for file, base in files: + target = Path(targetdir) / (base + ".tif") + logging.info(f"processing {file}") + + cope_command = [Path(path_cope), file, target] + cope_options + + logging.debug(f"running {cope_command}") + + # execute cope + try: + subprocess.run(cope_command, check=True) + except FileNotFoundError: + logging.error(f"cope executable was not found: {path_cope}") + exit(1) + except Exception as err: + logging.error(f"Unexpected {err=}, {type(err)=}") + raise + + # cope does not appear to make much use of exit codes, thus we check the existence of the expected tiff file here + if not target.is_file(): + logging.error(f"tiff file expected but not found: {target}") + exit(1) + else: + logging.debug(f"removing source package {target.with_suffix('')}") + shutil.rmtree(target.with_suffix('')) diff --git a/install/cope/eip_prepare_for_cope.py b/install/cope/eip_prepare_for_cope.py new file mode 100755 index 0000000..448c425 --- /dev/null +++ b/install/cope/eip_prepare_for_cope.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 + +# This script extracts eip files in a source directory to a target directory, renames a specific subdirectory and moves the original eip files + +import os +import shutil +from argparse import ArgumentParser +from pathlib import Path + + +def extract_and_rename_eip(source_dir, target_dir): + # Check if source directory exists + if not os.path.isdir(source_dir): + print("Source directory does not exist") + exit(1) + + # Create target directory if it does not exist + os.makedirs(target_dir, exist_ok=True) + + if source_dir.name.endswith("_master"): + raw_directory = Path.joinpath( + source_dir.parent, source_dir.name.replace("_master", "_raw")) + os.makedirs(raw_directory, exist_ok=True) + + for filename in os.listdir(source_dir): + if filename.endswith(".eip"): + file_path = os.path.join(source_dir, filename) + if os.path.isfile(file_path): + # Unzip eip to target directory + unzip_dir = os.path.join( + target_dir, os.path.splitext(filename)[0]) + shutil.unpack_archive(filename=file_path, + extract_dir=unzip_dir, format="zip") + + capture_one_dir = os.path.join(unzip_dir, "CaptureOne") + settings_old = os.path.join(capture_one_dir, "Settings153") + settings_new = os.path.join(capture_one_dir, "Settings131") + + # Check if CaptureOne directory exists + if not os.path.isdir(capture_one_dir): + print(f"{capture_one_dir} does not exist") + exit(1) + + # Rename setting directory to matching version for cope + if os.path.isdir(settings_old): + os.rename(settings_old, settings_new) + + os.rename(file_path, Path.joinpath(raw_directory, filename)) + + +def get_parser(): + """provides argument parser""" + parser = ArgumentParser() + parser.add_argument("-s", "--source", dest="source", + required=True, type=str, help="Source directory") + parser.add_argument("-t", "--target", dest="target", + required=True, type=str, help="Target directory") + return parser + + +if __name__ == "__main__": + parser = get_parser() + args = parser.parse_args() + + extract_and_rename_eip(Path(args.source), Path(args.target)) diff --git a/install/plugin_intranda_export_adm_bsme.xml b/install/plugin_intranda_export_adm_bsme.xml index e0295cb..b63adef 100644 --- a/install/plugin_intranda_export_adm_bsme.xml +++ b/install/plugin_intranda_export_adm_bsme.xml @@ -1,30 +1,46 @@ - /opt/digiverso/viewer/hotfolderMagazines/ - /opt/digiverso/viewer/hotfolderNewspapers/ + /opt/digiverso/export/bsme/mnt/export/Newspapers/ + /opt/digiverso/export/bsme/mnt/export/Magazines/ + /opt/digiverso/export/bsme/mnt/export/Positives/ + /opt/digiverso/export/bsme/mnt/export/Negatives/ + /opt/digiverso/export/bsme/mnt/export/Slides/ + /opt/digiverso/export/bsme/mnt/export/Generic/ + + /opt/digiverso/export/bsme/mnt/pdf/Newspapers/ + /opt/digiverso/export/bsme/mnt/pdf/Magazines/ + - https://adm.goobi.cloud/viewer/> - - - $(template.Rights to Use) - $(template.Rights Details) - Goobi - $(template.Media Type) - $(template.Media Group) - $(template.Source Organization) - $(template.Issue Frequency) - + $(template.Rights to Use) + $(template.Rights Details) + Goobi + $(template.Media Type) + $(template.Media Group) + $(template.Source Organization) + $(template.Issue Frequency) + $(template.Event Name) + $(template.Event Date) + $(template.Subject) + $(template.Photographer) + $(template.Persons in Image) + $(template.Locations) + $(template.Description) + $(template.Editor in Chief) + $(template.Format) - https://adm.goobi.cloud/viewer/sourcefile?id= - https://adm.goobi.cloud/viewer/piresolver?id= + https://adm.goobi.cloud/viewer/sourcefile?id= + + https://adm.goobi.cloud/viewer/piresolver?id= + https://adm.goobi.cloud/viewer/sourcefile?id=$(meta.topstruct.CatalogIDDigital).xml https://adm.goobi.cloud/viewer/sourcefile?id=$(meta.CatalogIDDigital).xml @@ -65,11 +81,11 @@ AnchorID AnchorTitle AccessConditionUse - AccessConditionDetails + AccessConditionDetails + Frequency - - + Newspaper Year @@ -78,5 +94,5 @@ NewspaperIssue NewspaperStub - - \ No newline at end of file + + diff --git a/module-base/pom.xml b/module-base/pom.xml index d980785..fb9636c 100644 --- a/module-base/pom.xml +++ b/module-base/pom.xml @@ -3,7 +3,7 @@ io.goobi.workflow.plugin plugin-export-adm-bsme - 24.04.30 + 24.05 plugin-export-adm-bsme-base jar diff --git a/module-base/src/main/java/de/intranda/goobi/plugins/AdmBsmeExportHelper.java b/module-base/src/main/java/de/intranda/goobi/plugins/AdmBsmeExportHelper.java index 1d953d3..54307ba 100644 --- a/module-base/src/main/java/de/intranda/goobi/plugins/AdmBsmeExportHelper.java +++ b/module-base/src/main/java/de/intranda/goobi/plugins/AdmBsmeExportHelper.java @@ -3,6 +3,8 @@ import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.Map; import org.apache.commons.lang.StringUtils; @@ -95,16 +97,54 @@ public static String getLanguageFullname(DocStruct ds, String field) { String lang = getMetdata(ds, field); switch (lang) { case "Arabic": - return "عربي – Arabic"; + return "عربي - Arabic"; case "ara": - return "عربي – Arabic"; + return "عربي - Arabic"; case "English": - return "انجليزي – English"; + return "انجليزي - English"; case "eng": - return "انجليزي – English"; + return "انجليزي - English"; case "ger": return "German"; } return lang; } + + /** + * convert the date from dd-mm-yyyy to format yyyy-mm-dd and give it back + * + * @param inputDate + * @return + */ + public static String convertDateFormatToDayMonthYear(String inputDate) { + return convertDateFormat(inputDate, "yyyy-MM-dd", "dd-MM-yyyy"); + } + + /** + * convert the date from yyyy-mm-dd to format dd-mm-yyyy and give it back + * + * @param inputDate + * @return + */ + public static String convertDateFormatToYearMonthDay(String inputDate) { + return convertDateFormat(inputDate, "dd-MM-yyyy", "yyyy-MM-dd"); + } + + /** + * convert the date from one format to the other + * + * @param inputDate + * @return + */ + private static String convertDateFormat(String inputDate, String inFormat, String outFormat) { + SimpleDateFormat inputFormatter = new SimpleDateFormat(inFormat); + SimpleDateFormat outputFormatter = new SimpleDateFormat(outFormat); + + try { + Date date = inputFormatter.parse(inputDate); + return outputFormatter.format(date); + } catch (Exception e) { + return inputDate; + } + } } diff --git a/module-base/src/main/java/de/intranda/goobi/plugins/AdmBsmeExportPlugin.java b/module-base/src/main/java/de/intranda/goobi/plugins/AdmBsmeExportPlugin.java index 124a69e..d030689 100644 --- a/module-base/src/main/java/de/intranda/goobi/plugins/AdmBsmeExportPlugin.java +++ b/module-base/src/main/java/de/intranda/goobi/plugins/AdmBsmeExportPlugin.java @@ -84,17 +84,33 @@ public boolean startExport(Process process, String destination) success = ne.startExport(); } else { - // do a regular export - IExportPlugin export = new ExportDms(); - export.setExportFulltext(false); - export.setExportImages(false); - success = export.startExport(process); - // for magazines do a specific export - if (success && "Periodical".equals(topStruct.getType().getName())) { + if ("Periodical".equals(topStruct.getType().getName())) { // if it is a Magazine - MagazineExporter me = new MagazineExporter(ConfigPlugins.getPluginConfig(title), process, prefs, dd); - success = me.startExport(); + + // do a regular export + IExportPlugin export = new ExportDms(); + export.setExportFulltext(false); + export.setExportImages(false); + success = export.startExport(process); + + // do the specific export + if (success) { + MagazineExporter ex = new MagazineExporter(ConfigPlugins.getPluginConfig(title), process, prefs, dd); + success = ex.startExport(); + } + + } + if ("AdmNegative".equals(topStruct.getType().getName())) { + // if it is a Negative + NegativeExporter ex = new NegativeExporter(ConfigPlugins.getPluginConfig(title), process, prefs, dd); + success = ex.startExport(); + } + + if ("AdmSlide".equals(topStruct.getType().getName())) { + // if it is a Slide + SlideExporter ex = new SlideExporter(ConfigPlugins.getPluginConfig(title), process, prefs, dd); + success = ex.startExport(); } } diff --git a/module-base/src/main/java/de/intranda/goobi/plugins/MagazineExporter.java b/module-base/src/main/java/de/intranda/goobi/plugins/MagazineExporter.java index 01b7b07..70024d0 100644 --- a/module-base/src/main/java/de/intranda/goobi/plugins/MagazineExporter.java +++ b/module-base/src/main/java/de/intranda/goobi/plugins/MagazineExporter.java @@ -12,6 +12,7 @@ import org.apache.commons.configuration.XMLConfiguration; import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine; +import org.apache.commons.lang.StringUtils; import org.goobi.beans.JournalEntry; import org.goobi.beans.Process; import org.goobi.production.enums.LogType; @@ -49,6 +50,7 @@ public class MagazineExporter { private DigitalDocument dd; private String viewerUrl; private String targetFolder; + private String pdfCopyFolder; // keep a list of all image files as they need to be renamed private Map fileMap; @@ -74,6 +76,7 @@ public MagazineExporter(XMLConfiguration config, Process process, Prefs prefs, D this.dd = dd; viewerUrl = config.getString("viewerUrl", "https://viewer.goobi.io"); targetFolder = config.getString("targetDirectoryMagazines", "/opt/digiverso/goobi/output/"); + pdfCopyFolder = config.getString("pdfCopyMagazines"); } /** @@ -121,8 +124,6 @@ public boolean startExport() { volume.addContent(new Element("Media_Source").setText(source)); volume.addContent(new Element("Media_type").setText(mediaType)); volume.addContent(new Element("Media_Group").setText(mediaGroup)); - // not needed anymore - // volume.addContent(new Element("Publication_ID").setText(volumeId)); volume.addContent(new Element("Publication_Name") .setText(AdmBsmeExportHelper.getMetdata(anchor, config.getString("/metadata/titleLabel")))); volume.addContent(new Element("Language") @@ -130,6 +131,8 @@ public boolean startExport() { volume.addContent( new Element("Source_Organization").setText(sourceOrganisation)); + // volume.addContent(new Element("Publication_ID").setText(volumeId)); + // add all journal entries as technical notes if (process.getJournal() != null) { Element technicalNotes = new Element("Technical_Notes"); @@ -151,7 +154,11 @@ public boolean startExport() { issue.addContent( new Element("issueNumber").setText(AdmBsmeExportHelper.getMetdata(topStruct, config.getString("/metadata/issueNumber")))); issue.addContent(new Element("issueID").setText(volumeId)); - issue.addContent(new Element("issueDate").setText(AdmBsmeExportHelper.getMetdata(topStruct, config.getString("/metadata/dateOfOrigin")))); + + // get the date and transform it from dd-mm-yyyy to yyyy-mm-dd + String date = AdmBsmeExportHelper.getMetdata(topStruct, config.getString("/metadata/dateOfOrigin")); + date = AdmBsmeExportHelper.convertDateFormatToYearMonthDay(date); + issue.addContent(new Element("issueDate").setText(date)); // get all title information String anchorTitle = AdmBsmeExportHelper.getMetdata(anchor, config.getString("/metadata/titleLabel")); @@ -309,6 +316,12 @@ public boolean startExport() { fout = new FileOutputStream(pdfi.getName()); new GetPdfAction().writePdf(map, ContentServerConfiguration.getInstance(), fout); fout.close(); + + // if a separate PDF copy shall be stored + if (StringUtils.isNotBlank(pdfCopyFolder)) { + StorageProvider.getInstance().copyFile(Paths.get(pdfi.getName()), Paths.get(pdfCopyFolder, volumeId + ".pdf")); + } + } catch (IOException | ContentLibException e) { log.error("Error while generating PDF files", e); return false; diff --git a/module-base/src/main/java/de/intranda/goobi/plugins/NegativeExporter.java b/module-base/src/main/java/de/intranda/goobi/plugins/NegativeExporter.java new file mode 100644 index 0000000..c96d0dc --- /dev/null +++ b/module-base/src/main/java/de/intranda/goobi/plugins/NegativeExporter.java @@ -0,0 +1,248 @@ +package de.intranda.goobi.plugins; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine; +import org.goobi.beans.JournalEntry; +import org.goobi.beans.Process; +import org.goobi.production.enums.LogType; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; + +import de.sub.goobi.helper.StorageProvider; +import de.sub.goobi.helper.StorageProviderInterface; +import de.sub.goobi.helper.VariableReplacer; +import de.sub.goobi.helper.exceptions.DAOException; +import de.sub.goobi.helper.exceptions.SwapException; +import de.unigoettingen.sub.commons.contentlib.exceptions.ImageManagerException; +import de.unigoettingen.sub.commons.contentlib.imagelib.ImageInterpreter; +import de.unigoettingen.sub.commons.contentlib.imagelib.ImageManager; +import lombok.Getter; +import lombok.extern.log4j.Log4j2; +import net.xeoh.plugins.base.annotations.PluginImplementation; +import ugh.dl.DigitalDocument; +import ugh.dl.DocStruct; +import ugh.dl.Prefs; +import ugh.dl.Reference; + +@PluginImplementation +@Log4j2 +public class NegativeExporter { + + private XMLConfiguration config; + private Process process; + private Prefs prefs; + private DigitalDocument dd; + private String targetFolder; + + // keep a list of all image files as they need to be renamed + private Map fileMap; + private int fileCounter; + private VariableReplacer vr; + + @Getter + private List problems; + + /** + * Constructor + * + * @param config + * @param process + * @param prefs + * @param dd + */ + public NegativeExporter(XMLConfiguration config, Process process, Prefs prefs, DigitalDocument dd) { + this.config = config; + config.setExpressionEngine(new XPathExpressionEngine()); + this.process = process; + this.prefs = prefs; + this.dd = dd; + targetFolder = config.getString("targetDirectoryNegatives", "/opt/digiverso/goobi/output/"); + } + + /** + * Do the actual export for a newspaper volume + * + * @param process + * @param destination + * @return + */ + public boolean startExport() { + vr = new VariableReplacer(dd, prefs, process, null); + problems = new ArrayList<>(); + fileMap = new HashMap(); + fileCounter = 0; + log.debug("Export directory for AdmBsmeExportPlugin: " + targetFolder); + DocStruct topStruct = dd.getLogicalDocStruct(); + + // prepare xml document + Document doc = new Document(); + doc.setRootElement(new Element("envelope")); + + // add volume information + Element info = new Element("envelopeInfo"); + doc.getRootElement().addContent(info); + String identifier = AdmBsmeExportHelper.getMetdata(topStruct, config.getString("/metadata/identifier")); + + String rightsToUse = vr.replace(config.getString("/rightsToUse")); + String rightsDetails = vr.replace(config.getString("/rightsDetails")); + String source = vr.replace(config.getString("/source")); + String mediaType = vr.replace(config.getString("/mediaType")); + String mediaGroup = vr.replace(config.getString("/mediaGroup")); + String sourceOrganisation = vr.replace(config.getString("/sourceOrganisation")); + String eventDate = vr.replace(config.getString("/eventDate")); + String eventName = vr.replace(config.getString("/eventName")); + String subject = vr.replace(config.getString("/subject")); + String photographer = vr.replace(config.getString("/photographer")); + String personsInImage = vr.replace(config.getString("/personsInImage")); + String locations = vr.replace(config.getString("/locations")); + String description = vr.replace(config.getString("/description")); + String editorInChief = vr.replace(config.getString("/editorInChief")); + String format = vr.replace(config.getString("/format")); + + info.addContent(new Element("Rights_to_Use").setText(rightsToUse)); + info.addContent(new Element("Right_Details").setText(rightsDetails)); + info.addContent(new Element("Media_Source").setText(source)); + info.addContent(new Element("Media_type").setText(mediaType)); + info.addContent(new Element("Envelope_Barcode").setText(identifier)); + info.addContent(new Element("Publication_Name") + .setText(AdmBsmeExportHelper.getMetdata(topStruct, config.getString("/metadata/titleLabel")))); + info.addContent( + new Element("Source_Organization").setText(sourceOrganisation)); + info.addContent(new Element("Event_Date").setText(eventDate)); + info.addContent(new Element("Event_Name").setText(eventName)); + info.addContent(new Element("Subject").setText(subject)); + info.addContent(new Element("Photographer").setText(photographer)); + info.addContent(new Element("Film_Format").setText(format)); + info.addContent(new Element("Persons_in_Image").setText(personsInImage)); + info.addContent(new Element("Editor_in_Chief").setText(editorInChief)); + info.addContent(new Element("location").setText(locations)); + info.addContent(new Element("Description").setText(description)); + + // info.addContent(new Element("Media_Group").setText(mediaGroup)); + + // add all journal entries as technical notes + if (process.getJournal() != null) { + Element technicalNotes = new Element("Technical_Notes"); + for (JournalEntry je : process.getJournal()) { + if (je.getType() == LogType.USER) { + technicalNotes.addContent(new Element("Entry").setAttribute("date", je.getFormattedCreationDate()) + .setAttribute("type", je.getType().getTitle()) + .setText(je.getFormattedContent())); + } + } + info.addContent(technicalNotes); + } else { + info.addContent(new Element("Technical_Notes").setText("- no entry available -")); + } + + // add file information + Element files = new Element("Images"); + doc.getRootElement().addContent(files); + + List refs = topStruct.getAllToReferences("logical_physical"); + if (refs != null) { + for (Reference ref : refs) { + DocStruct page = ref.getTarget(); + String realFileName = page.getImageName(); + String realFileNameWithoutExtension = realFileName.substring(0, realFileName.indexOf(".")); + + // get the new file name for the image and reuse if created previously + String exportFileName = fileMap.get(realFileNameWithoutExtension); + if (exportFileName == null) { + String counter = String.format("%04d", ++fileCounter); + exportFileName = identifier + "-" + counter; + fileMap.put(realFileNameWithoutExtension, exportFileName); + } + + // add file element + Element file = new Element("Image"); + file.setAttribute("id", String.format("%04d", fileCounter)); + Element master = new Element("master"); + + // add image information + try { + File realFile = new File(process.getImagesOrigDirectory(false), + realFileNameWithoutExtension + ".tif"); + try (ImageManager sourcemanager = new ImageManager(realFile.toURI())) { + ImageInterpreter si = sourcemanager.getMyInterpreter(); + + // MimeType + // master.setAttribute("Format", si.getFormatType().getFormat().getMimeType()); + master.addContent(new Element("Format").setText(si.getFormatType().getFormat().getMimeType())); + + // Unit for the resolution, always ppi + // master.setAttribute("ResolutionUnit", "PPI"); + master.addContent(new Element("ResolutionUnit").setText("PPI")); + + // Resolution + // master.setAttribute("Resolution", String.valueOf(si.getOriginalImageXResolution())); + master.addContent(new Element("Resolution").setText(String.valueOf(si.getOriginalImageXResolution()))); + + // ColorDepth + // master.setAttribute("BitDepth", String.valueOf(si.getColordepth())); + master.addContent(new Element("BitDepth").setText(String.valueOf(si.getColordepth()))); + + // bitonal, grey, "color" + // master.setAttribute("ColorSpace", si.getFormatType().getColortype().getLabel()); + master.addContent(new Element("ColorSpace").setText(si.getFormatType().getColortype().getLabel())); + + // Scanning device + master.addContent(new Element("ScanningDevice").setText(vr.replace("${process.Capturing device}"))); + + // Scanning device id + String scanningDeviceId = "- no serial number available -"; //si.getMetadata().toString(); + master.addContent(new Element("ScanningDeviceID").setText(scanningDeviceId)); + + // Width + master.addContent(new Element("Width").setText(String.valueOf(si.getOriginalImageWidth()))); + + // Height + master.addContent(new Element("Height").setText(String.valueOf(si.getOriginalImageHeight()))); + sourcemanager.close(); + } + } catch (IOException | SwapException | DAOException | ImageManagerException e) { + log.error("Error while reading image metadata", e); + return false; + } + + master.addContent(new Element("file").setText(exportFileName + ".tif")); + file.addContent(master); + files.addContent(file); + } + } + + // write the xml file + XMLOutputter xmlOutputter = new XMLOutputter(); + xmlOutputter.setFormat(Format.getPrettyFormat()); + File xmlfile = new File(targetFolder + identifier + ".xml"); + try (FileOutputStream fileOutputStream = new FileOutputStream(xmlfile)) { + xmlOutputter.output(doc, fileOutputStream); + } catch (IOException e) { + log.error("Error writing the simple xml file", e); + return false; + } + + try { + // copy all important files to target folder + AdmBsmeExportHelper.copyFolderContent(process.getImagesOrigDirectory(false), "tif", fileMap, targetFolder); + StorageProviderInterface sp = StorageProvider.getInstance(); + + } catch (IOException | SwapException | DAOException e) { + log.error("Error while copying the image files to export folder", e); + return false; + } + + return true; + } + +} \ No newline at end of file diff --git a/module-base/src/main/java/de/intranda/goobi/plugins/NewspaperExporter.java b/module-base/src/main/java/de/intranda/goobi/plugins/NewspaperExporter.java index c51b132..dc82852 100644 --- a/module-base/src/main/java/de/intranda/goobi/plugins/NewspaperExporter.java +++ b/module-base/src/main/java/de/intranda/goobi/plugins/NewspaperExporter.java @@ -3,6 +3,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -10,6 +11,7 @@ import org.apache.commons.configuration.XMLConfiguration; import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine; +import org.apache.commons.lang.StringUtils; import org.goobi.beans.JournalEntry; import org.goobi.beans.Process; import org.goobi.production.enums.LogType; @@ -19,6 +21,7 @@ import org.jdom2.output.XMLOutputter; import de.sub.goobi.helper.Helper; +import de.sub.goobi.helper.StorageProvider; import de.sub.goobi.helper.VariableReplacer; import de.sub.goobi.helper.exceptions.DAOException; import de.sub.goobi.helper.exceptions.SwapException; @@ -50,6 +53,7 @@ public class NewspaperExporter { private DigitalDocument dd; private String viewerUrl; private String targetFolder; + private String pdfCopyFolder; // keep a list of all image files as they need to be renamed private Map fileMap; @@ -76,6 +80,7 @@ public NewspaperExporter(XMLConfiguration config, Process process, Prefs prefs, this.dd = dd; viewerUrl = config.getString("viewerUrl", "https://viewer.goobi.io"); targetFolder = config.getString("targetDirectoryNewspapers", "/opt/digiverso/goobi/output/"); + pdfCopyFolder = config.getString("pdfCopyNewspapers"); pdfIssues = new ArrayList<>(); } @@ -129,8 +134,6 @@ public boolean startExport() { volume.addContent(new Element("Media_Source").setText(source)); volume.addContent(new Element("Media_type").setText(mediaType)); volume.addContent(new Element("Media_Group").setText(mediaGroup)); - // not needed anymore - // volume.addContent(new Element("Publication_ID").setText(volumeId)); volume.addContent(new Element("Publication_Name") .setText(AdmBsmeExportHelper.getMetdata(anchor, config.getString("/metadata/titleLabel")))); volume.addContent(new Element("Language") @@ -138,6 +141,8 @@ public boolean startExport() { volume.addContent( new Element("Source_Organization").setText(sourceOrganisation)); + // volume.addContent(new Element("Publication_ID").setText(volumeId)); + // add all journal entries as technical notes if (process.getJournal() != null) { Element technicalNotes = new Element("Technical_Notes"); @@ -171,10 +176,14 @@ public boolean startExport() { String issueTitle = AdmBsmeExportHelper.getCleanIssueLabel(AdmBsmeExportHelper.getMetdata(ds, config.getString("/metadata/titleLabel"))); + // convert date from from yyyy-mm-dd to dd-mm-yyyy + String date = AdmBsmeExportHelper.getMetdata(ds, config.getString("/metadata/issueDate")); + date = AdmBsmeExportHelper.convertDateFormatToDayMonthYear(date); + // add an English title - issue.addContent(new Element("issueTitleENG").setText(anchorTitleEng + "-" + issueTitle)); + issue.addContent(new Element("issueTitleENG").setText(anchorTitleEng + "-" + date)); // add an Arabic title - issue.addContent(new Element("issueTitleARA").setText(issueTitle + "-" + anchorTitleAra)); + issue.addContent(new Element("issueTitleARA").setText(date + "-" + anchorTitleAra)); issue.addContent(new Element("issueDate").setText(AdmBsmeExportHelper.getMetdata(ds, config.getString("/metadata/issueDate")))); issue.addContent(new Element("Open_In_Viewer").setText(viewerUrl + volumeId + "-" + simpleDate)); @@ -320,6 +329,13 @@ public boolean startExport() { fout = new FileOutputStream(pi.getName()); new GetPdfAction().writePdf(map, ContentServerConfiguration.getInstance(), fout); fout.close(); + + // if a separate PDF copy shall be stored + if (StringUtils.isNotBlank(pdfCopyFolder)) { + StorageProvider.getInstance() + .copyFile(Paths.get(pi.getName()), Paths.get(pdfCopyFolder, Paths.get(pi.getName()).getFileName().toString())); + } + } catch (IOException | ContentLibException e) { log.error("Error while generating PDF files", e); return false; diff --git a/module-base/src/main/java/de/intranda/goobi/plugins/SlideExporter.java b/module-base/src/main/java/de/intranda/goobi/plugins/SlideExporter.java new file mode 100644 index 0000000..4c5b7d1 --- /dev/null +++ b/module-base/src/main/java/de/intranda/goobi/plugins/SlideExporter.java @@ -0,0 +1,242 @@ +package de.intranda.goobi.plugins; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine; +import org.goobi.beans.JournalEntry; +import org.goobi.beans.Process; +import org.goobi.production.enums.LogType; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; + +import de.sub.goobi.helper.StorageProvider; +import de.sub.goobi.helper.StorageProviderInterface; +import de.sub.goobi.helper.VariableReplacer; +import de.sub.goobi.helper.exceptions.DAOException; +import de.sub.goobi.helper.exceptions.SwapException; +import de.unigoettingen.sub.commons.contentlib.exceptions.ImageManagerException; +import de.unigoettingen.sub.commons.contentlib.imagelib.ImageInterpreter; +import de.unigoettingen.sub.commons.contentlib.imagelib.ImageManager; +import lombok.Getter; +import lombok.extern.log4j.Log4j2; +import net.xeoh.plugins.base.annotations.PluginImplementation; +import ugh.dl.DigitalDocument; +import ugh.dl.DocStruct; +import ugh.dl.Prefs; +import ugh.dl.Reference; + +@PluginImplementation +@Log4j2 +public class SlideExporter { + + private XMLConfiguration config; + private Process process; + private Prefs prefs; + private DigitalDocument dd; + private String targetFolder; + + // keep a list of all image files as they need to be renamed + private Map fileMap; + private int fileCounter; + private VariableReplacer vr; + + @Getter + private List problems; + + /** + * Constructor + * + * @param config + * @param process + * @param prefs + * @param dd + */ + public SlideExporter(XMLConfiguration config, Process process, Prefs prefs, DigitalDocument dd) { + this.config = config; + config.setExpressionEngine(new XPathExpressionEngine()); + this.process = process; + this.prefs = prefs; + this.dd = dd; + targetFolder = config.getString("targetDirectorySlides", "/opt/digiverso/goobi/output/"); + } + + /** + * Do the actual export for a newspaper volume + * + * @param process + * @param destination + * @return + */ + public boolean startExport() { + vr = new VariableReplacer(dd, prefs, process, null); + problems = new ArrayList<>(); + fileMap = new HashMap(); + fileCounter = 0; + log.debug("Export directory for AdmBsmeExportPlugin: " + targetFolder); + DocStruct topStruct = dd.getLogicalDocStruct(); + + // prepare xml document + Document doc = new Document(); + doc.setRootElement(new Element("image")); + + // add volume information + Element info = new Element("SlideInfo"); + doc.getRootElement().addContent(info); + String identifier = AdmBsmeExportHelper.getMetdata(topStruct, config.getString("/metadata/identifier")); + + String rightsToUse = vr.replace(config.getString("/rightsToUse")); + String rightsDetails = vr.replace(config.getString("/rightsDetails")); + String source = vr.replace(config.getString("/source")); + String mediaType = vr.replace(config.getString("/mediaType")); + String mediaGroup = vr.replace(config.getString("/mediaGroup")); + String sourceOrganisation = vr.replace(config.getString("/sourceOrganisation")); + String eventDate = vr.replace(config.getString("/eventDate")); + String eventName = vr.replace(config.getString("/eventName")); + String subject = vr.replace(config.getString("/subject")); + String photographer = vr.replace(config.getString("/photographer")); + String personsInImage = vr.replace(config.getString("/personsInImage")); + String locations = vr.replace(config.getString("/locations")); + String description = vr.replace(config.getString("/description")); + String editorInChief = vr.replace(config.getString("/editorInChief")); + String format = vr.replace(config.getString("/format")); + + info.addContent(new Element("Rights_to_Use").setText(rightsToUse)); + info.addContent(new Element("Right_Details").setText(rightsDetails)); + info.addContent(new Element("Media_Source").setText(source)); + info.addContent(new Element("Media_type").setText(mediaType)); + info.addContent(new Element("Publication_Name") + .setText(AdmBsmeExportHelper.getMetdata(topStruct, config.getString("/metadata/titleLabel")))); + info.addContent( + new Element("Source_Organization").setText(sourceOrganisation)); + info.addContent(new Element("Barcode").setText(identifier)); + info.addContent(new Element("Event_Date").setText(eventDate)); + info.addContent(new Element("Event_Name").setText(eventName)); + info.addContent(new Element("Photographer").setText(photographer)); + info.addContent(new Element("Format").setText(format)); + info.addContent(new Element("Persons_in_Image").setText(personsInImage)); + info.addContent(new Element("location").setText(locations)); + info.addContent(new Element("Description").setText(description)); + + // info.addContent(new Element("Editor_in_Chief").setText(editorInChief)); + // info.addContent(new Element("Media_Group").setText(mediaGroup)); + // info.addContent(new Element("Subject").setText(subject)); + + // add all journal entries as technical notes + if (process.getJournal() != null) { + Element technicalNotes = new Element("Technical_Notes"); + for (JournalEntry je : process.getJournal()) { + if (je.getType() == LogType.USER) { + technicalNotes.addContent(new Element("Entry").setAttribute("date", je.getFormattedCreationDate()) + .setAttribute("type", je.getType().getTitle()) + .setText(je.getFormattedContent())); + } + } + info.addContent(technicalNotes); + } else { + info.addContent(new Element("Technical_Notes").setText("- no entry available -")); + } + + // add file information + List refs = topStruct.getAllToReferences("logical_physical"); + if (refs != null) { + for (Reference ref : refs) { + DocStruct page = ref.getTarget(); + String realFileName = page.getImageName(); + String realFileNameWithoutExtension = realFileName.substring(0, realFileName.indexOf(".")); + + // get the new file name for the image and reuse if created previously + String exportFileName = fileMap.get(realFileNameWithoutExtension); + if (exportFileName == null) { + String counter = String.format("%04d", ++fileCounter); + exportFileName = identifier + "-" + counter; + fileMap.put(realFileNameWithoutExtension, exportFileName); + } + + // add file element + Element master = new Element("master"); + + // add image information + try { + File realFile = new File(process.getImagesOrigDirectory(false), + realFileNameWithoutExtension + ".tif"); + try (ImageManager sourcemanager = new ImageManager(realFile.toURI())) { + ImageInterpreter si = sourcemanager.getMyInterpreter(); + + // MimeType + // master.setAttribute("Format", si.getFormatType().getFormat().getMimeType()); + master.addContent(new Element("Format").setText(si.getFormatType().getFormat().getMimeType())); + + // Unit for the resolution, always ppi + // master.setAttribute("ResolutionUnit", "PPI"); + master.addContent(new Element("ResolutionUnit").setText("PPI")); + + // Resolution + // master.setAttribute("Resolution", String.valueOf(si.getOriginalImageXResolution())); + master.addContent(new Element("Resolution").setText(String.valueOf(si.getOriginalImageXResolution()))); + + // ColorDepth + // master.setAttribute("BitDepth", String.valueOf(si.getColordepth())); + master.addContent(new Element("BitDepth").setText(String.valueOf(si.getColordepth()))); + + // bitonal, grey, "color" + // master.setAttribute("ColorSpace", si.getFormatType().getColortype().getLabel()); + master.addContent(new Element("ColorSpace").setText(si.getFormatType().getColortype().getLabel())); + + // Scanning device + master.addContent(new Element("ScanningDevice").setText(vr.replace("${process.Capturing device}"))); + + // Scanning device id + String scanningDeviceId = "- no serial number available -"; //si.getMetadata().toString(); + master.addContent(new Element("ScanningDeviceID").setText(scanningDeviceId)); + + // Width + master.addContent(new Element("Width").setText(String.valueOf(si.getOriginalImageWidth()))); + + // Height + master.addContent(new Element("Height").setText(String.valueOf(si.getOriginalImageHeight()))); + sourcemanager.close(); + } + } catch (IOException | SwapException | DAOException | ImageManagerException e) { + log.error("Error while reading image metadata", e); + return false; + } + + master.addContent(new Element("file").setText(exportFileName + ".tif")); + doc.getRootElement().addContent(master); + } + } + + // write the xml file + XMLOutputter xmlOutputter = new XMLOutputter(); + xmlOutputter.setFormat(Format.getPrettyFormat()); + File xmlfile = new File(targetFolder + identifier + ".xml"); + try (FileOutputStream fileOutputStream = new FileOutputStream(xmlfile)) { + xmlOutputter.output(doc, fileOutputStream); + } catch (IOException e) { + log.error("Error writing the simple xml file", e); + return false; + } + + try { + // copy all important files to target folder + AdmBsmeExportHelper.copyFolderContent(process.getImagesOrigDirectory(false), "tif", fileMap, targetFolder); + StorageProviderInterface sp = StorageProvider.getInstance(); + + } catch (IOException | SwapException | DAOException e) { + log.error("Error while copying the image files to export folder", e); + return false; + } + + return true; + } + +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 72f923f..3601dc5 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ io.goobi.workflow workflow-base - 24.04.30 + 24.05 io.goobi.workflow.plugin