diff --git a/Kitodo-API/src/main/java/org/kitodo/api/command/CommandInterface.java b/Kitodo-API/src/main/java/org/kitodo/api/command/CommandInterface.java index bff46b96a6e..a1f24896a36 100644 --- a/Kitodo-API/src/main/java/org/kitodo/api/command/CommandInterface.java +++ b/Kitodo-API/src/main/java/org/kitodo/api/command/CommandInterface.java @@ -16,11 +16,9 @@ public interface CommandInterface { /** * Runs a given command. * - * @param id - * The id, to identify the command and it's results. * @param command * The command as a String. * @return A commandResult, which contains id and result messages. */ - CommandResult runCommand(Integer id, String command); + CommandResult runCommand(String command); } diff --git a/Kitodo-API/src/main/java/org/kitodo/api/command/CommandResult.java b/Kitodo-API/src/main/java/org/kitodo/api/command/CommandResult.java index 7cc294b3f9e..7b89ed36458 100644 --- a/Kitodo-API/src/main/java/org/kitodo/api/command/CommandResult.java +++ b/Kitodo-API/src/main/java/org/kitodo/api/command/CommandResult.java @@ -16,9 +16,6 @@ public class CommandResult { - /** The id of the command. */ - private Integer id; - /** The command as a String. */ private String command; @@ -31,8 +28,6 @@ public class CommandResult { /** * Constructor. * - * @param id - * The id. * @param command * The command. * @param successful @@ -40,22 +35,12 @@ public class CommandResult { * @param messages * The resultMessages */ - public CommandResult(Integer id, String command, boolean successful, List messages) { - this.id = id; + public CommandResult(String command, boolean successful, List messages) { this.command = command; this.successful = successful; this.messages = messages; } - /** - * Gets the id. - * - * @return The id. - */ - public Integer getId() { - return id; - } - /** * Gets the command. * @@ -99,8 +84,7 @@ public boolean equals(Object object) { if (object instanceof CommandResult) { CommandResult that = (CommandResult) object; - return this.id.equals(that.id) - && this.successful == that.successful + return this.successful == that.successful && this.command.equals(that.command) && this.messages.equals(that.messages); } @@ -109,6 +93,6 @@ public boolean equals(Object object) { @Override public int hashCode() { - return Objects.hash(id, successful, command, messages); + return Objects.hash(successful, command, messages); } } diff --git a/Kitodo-API/src/main/java/org/kitodo/api/dataformat/Workpiece.java b/Kitodo-API/src/main/java/org/kitodo/api/dataformat/Workpiece.java index 797a06a59ed..e97377163f3 100644 --- a/Kitodo-API/src/main/java/org/kitodo/api/dataformat/Workpiece.java +++ b/Kitodo-API/src/main/java/org/kitodo/api/dataformat/Workpiece.java @@ -12,7 +12,7 @@ package org.kitodo.api.dataformat; import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; import java.util.Comparator; import java.util.GregorianCalendar; import java.util.List; @@ -180,15 +180,16 @@ public List getAllLogicalDivisions() { /** * Returns all child physical divisions of the physical division of the workpiece with - * type "page" sorted by their {@code order} as a flat list. The root media + * type "page" or "track" sorted by their {@code order} as a flat list. The root media * unit is not contained. The list isn’t backed by the physical divisions, which * means that insertions and deletions in the list would not change the * physical divisions. Therefore, a list that cannot be modified is returned. * - * @return all physical divisions with type "page", sorted by their {@code order} + * @return all physical divisions with type "page" or "track", sorted by their {@code order} */ - public List getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted() { - return getAllPhysicalDivisionChildrenFilteredByTypes(Collections.singletonList(PhysicalDivision.TYPE_PAGE)); + public List getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack() { + return getAllPhysicalDivisionChildrenFilteredByTypes( + Arrays.asList(PhysicalDivision.TYPE_PAGE, PhysicalDivision.TYPE_TRACK)); } /** diff --git a/Kitodo-API/src/main/java/org/kitodo/api/docket/DocketData.java b/Kitodo-API/src/main/java/org/kitodo/api/docket/DocketData.java index 7b2d9b0e349..ef6b2c78272 100644 --- a/Kitodo-API/src/main/java/org/kitodo/api/docket/DocketData.java +++ b/Kitodo-API/src/main/java/org/kitodo/api/docket/DocketData.java @@ -39,8 +39,8 @@ public class DocketData { /** The creation Date of the process. */ private String creationDate; - /** A comment. */ - private String comment; + /** The comments. */ + private List comments = new ArrayList<>(); /** The template properties. */ private List templateProperties; @@ -185,22 +185,22 @@ public void setCreationDate(String creationDate) { } /** - * Gets the comment. + * Gets the comments. * - * @return The comment. + * @return The comments. */ - public String getComment() { - return comment; + public List getComments() { + return comments; } /** - * Sets the comment. + * Sets the comments. * - * @param comment - * The comment. + * @param comments + * The comments. */ - public void setComment(String comment) { - this.comment = comment; + public void setComments(List comments) { + this.comments = comments; } /** diff --git a/Kitodo-API/src/main/java/org/kitodo/utils/MediaUtil.java b/Kitodo-API/src/main/java/org/kitodo/utils/MediaUtil.java new file mode 100644 index 00000000000..1b98d1ba10b --- /dev/null +++ b/Kitodo-API/src/main/java/org/kitodo/utils/MediaUtil.java @@ -0,0 +1,94 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * This file is part of the Kitodo project. + * + * It is licensed under GNU General Public License version 3 or later. + * + * For the full copyright and license information, please read the + * GPL3-License.txt file that was distributed with this source code. + */ + +package org.kitodo.utils; + +import java.util.Objects; + +import org.kitodo.api.dataformat.PhysicalDivision; + +public class MediaUtil { + + public static final String MIME_TYPE_AUDIO_PREFIX = "audio"; + public static final String MIME_TYPE_IMAGE_PREFIX = "image"; + public static final String MIME_TYPE_VIDEO_PREFIX = "video"; + + /** + * Private constructor to hide the implicit public one. + */ + private MediaUtil() { + + } + + /** + * Check if mime type starts with {@link org.kitodo.utils.MediaUtil#MIME_TYPE_AUDIO_PREFIX} or + * {@link org.kitodo.utils.MediaUtil#MIME_TYPE_VIDEO_PREFIX}. + * + * @param mimeType + * The mime type to check + * @return True if mime type starts with {@link org.kitodo.utils.MediaUtil#MIME_TYPE_AUDIO_PREFIX} or + * {@link org.kitodo.utils.MediaUtil#MIME_TYPE_VIDEO_PREFIX}. + */ + public static boolean isAudioOrVideo(String mimeType) { + return isAudio(mimeType) || isVideo(mimeType); + } + + /** + * Check if mime type starts with {@link org.kitodo.utils.MediaUtil#MIME_TYPE_AUDIO_PREFIX}. + * + * @param mimeType + * The mime type to check + * @return True if mime type starts with {@link org.kitodo.utils.MediaUtil#MIME_TYPE_AUDIO_PREFIX} + */ + public static boolean isAudio(String mimeType) { + return Objects.nonNull(mimeType) && mimeType.startsWith(MIME_TYPE_AUDIO_PREFIX); + } + + /** + * Check if mime type starts with {@link org.kitodo.utils.MediaUtil#MIME_TYPE_IMAGE_PREFIX}. + * + * @param mimeType + * The mime type to check + * @return True if mime type starts with {@link org.kitodo.utils.MediaUtil#MIME_TYPE_IMAGE_PREFIX} + */ + public static boolean isImage(String mimeType) { + return Objects.nonNull(mimeType) && mimeType.startsWith(MIME_TYPE_IMAGE_PREFIX); + } + + /** + * Check if mime type starts with {@link org.kitodo.utils.MediaUtil#MIME_TYPE_VIDEO_PREFIX}. + * + * @param mimeType + * The mime type to check + * @return True if mime type starts with {@link org.kitodo.utils.MediaUtil#MIME_TYPE_VIDEO_PREFIX} + */ + public static boolean isVideo(String mimeType) { + return Objects.nonNull(mimeType) && mimeType.startsWith(MIME_TYPE_VIDEO_PREFIX); + } + + /** + * Get the type of {@link org.kitodo.api.dataformat.PhysicalDivision} by mime type. + * + * @param mimeType + * The mime type to get the physical division type for + * @return The type of the {@link org.kitodo.api.dataformat.PhysicalDivision} + */ + public static String getPhysicalDivisionTypeOfMimeType(String mimeType) { + if (isImage(mimeType)) { + return PhysicalDivision.TYPE_PAGE; + } + if (isAudioOrVideo(mimeType)) { + return PhysicalDivision.TYPE_TRACK; + } + return PhysicalDivision.TYPE_OTHER; + } + +} diff --git a/Kitodo-API/src/test/java/org/kitodo/utils/MediaUtilTest.java b/Kitodo-API/src/test/java/org/kitodo/utils/MediaUtilTest.java new file mode 100644 index 00000000000..d106a76074c --- /dev/null +++ b/Kitodo-API/src/test/java/org/kitodo/utils/MediaUtilTest.java @@ -0,0 +1,52 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * This file is part of the Kitodo project. + * + * It is licensed under GNU General Public License version 3 or later. + * + * For the full copyright and license information, please read the + * GPL3-License.txt file that was distributed with this source code. + */ + +package org.kitodo.utils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.kitodo.api.dataformat.PhysicalDivision; + +public class MediaUtilTest { + + /** + * Test detection of mime type. + */ + @Test + public void testMimeTypeDetection() { + assertTrue(MediaUtil.isAudio("audio/mp3")); + assertFalse(MediaUtil.isVideo("image/jpeg")); + + assertTrue(MediaUtil.isImage("image/jpeg")); + assertFalse(MediaUtil.isImage("video/mp4")); + + assertTrue(MediaUtil.isVideo("video/mp4")); + assertFalse(MediaUtil.isVideo("image/jpeg")); + + assertTrue(MediaUtil.isAudioOrVideo("audio/mp3")); + assertTrue(MediaUtil.isAudioOrVideo("video/mp4")); + assertFalse(MediaUtil.isAudioOrVideo("image/jpeg")); + } + + /** + * Test getting the type of the {@link org.kitodo.api.dataformat.PhysicalDivision}. + */ + @Test + public void testGettingPhysicalDivisionTypeByMimeType() { + assertEquals(PhysicalDivision.TYPE_PAGE, MediaUtil.getPhysicalDivisionTypeOfMimeType("image/jpeg")); + assertEquals(PhysicalDivision.TYPE_TRACK, MediaUtil.getPhysicalDivisionTypeOfMimeType("audio/mp3")); + assertEquals(PhysicalDivision.TYPE_TRACK, MediaUtil.getPhysicalDivisionTypeOfMimeType("video/mp4")); + assertEquals(PhysicalDivision.TYPE_OTHER, MediaUtil.getPhysicalDivisionTypeOfMimeType("application/pdf")); + } +} diff --git a/Kitodo-Command/src/main/java/org/kitodo/command/Command.java b/Kitodo-Command/src/main/java/org/kitodo/command/Command.java index 5ec8ec1ad2b..835ddaeee25 100644 --- a/Kitodo-Command/src/main/java/org/kitodo/command/Command.java +++ b/Kitodo-Command/src/main/java/org/kitodo/command/Command.java @@ -31,14 +31,12 @@ public class Command implements CommandInterface { /** * Method executes a script. * - * @param id - * The id, to identify the command and it's results. * @param command * The command as a String. * @return The command result. */ @Override - public CommandResult runCommand(Integer id, String command) { + public CommandResult runCommand(String command) { CommandResult commandResult; Process process; String[] callSequence = command.split("[\\r\\n\\s]+"); @@ -53,17 +51,17 @@ public CommandResult runCommand(Integer id, String command) { outputMessage.addAll(errorMessage); - commandResult = new CommandResult(id, command, errCode == 0, outputMessage); + commandResult = new CommandResult(command, errCode == 0, outputMessage); if (commandResult.isSuccessful()) { - logger.info("Execution of Command {} {} was successful!: {}", commandResult.getId(), + logger.info("Execution of Command {} was successful!: {}", commandResult.getCommand(), commandResult.getMessages()); } else { - logger.error("Execution of Command {} {} failed!: {}", commandResult.getId(), + logger.error("Execution of Command {} failed!: {}", commandResult.getCommand(), commandResult.getMessages()); } } } catch (InterruptedException e) { - commandResult = new CommandResult(id, command, false, Collections.singletonList(e.getMessage())); + commandResult = new CommandResult(command, false, Collections.singletonList(e.getMessage())); logger.error("Execution of Command Thread was interrupted!"); Thread.currentThread().interrupt(); return commandResult; @@ -71,8 +69,8 @@ public CommandResult runCommand(Integer id, String command) { List errorMessages = new ArrayList<>(); errorMessages.add(e.getCause().toString()); errorMessages.add(e.getMessage()); - commandResult = new CommandResult(id, command, false, errorMessages); - logger.error("Execution of Command {} {} failed!: {}", commandResult.getId(), commandResult.getCommand(), + commandResult = new CommandResult(command, false, errorMessages); + logger.error("Execution of Command {} failed!: {}", commandResult.getCommand(), commandResult.getMessages()); return commandResult; } diff --git a/Kitodo-Command/src/test/java/org/kitodo/command/CommandTest.java b/Kitodo-Command/src/test/java/org/kitodo/command/CommandTest.java index b32a98ccd29..a09e467dbe3 100644 --- a/Kitodo-Command/src/test/java/org/kitodo/command/CommandTest.java +++ b/Kitodo-Command/src/test/java/org/kitodo/command/CommandTest.java @@ -31,7 +31,6 @@ public class CommandTest { private static String scriptExtension; - private int processId = 3; private static boolean windows = false; private static File workingScript = new File( System.getProperty("user.dir") + "/src/test/resources/working_script.sh"); @@ -70,7 +69,7 @@ public void shouldRunCommand() { Command command = new Command(); String commandString = "src/test/resources/working_script" + scriptExtension; - CommandResult commandResult = command.runCommand(processId, commandString); + CommandResult commandResult = command.runCommand(commandString); List expectedMessages = new ArrayList<>(); if (windows) { @@ -80,7 +79,7 @@ public void shouldRunCommand() { expectedMessages.add("Hello World"); - CommandResult expectedCommandResult = new CommandResult(processId, commandString, true, expectedMessages); + CommandResult expectedCommandResult = new CommandResult(commandString, true, expectedMessages); assertEquals("successful booleans of CommandResults are not identical", expectedCommandResult.isSuccessful(), commandResult.isSuccessful()); @@ -95,9 +94,9 @@ public void shouldNotRunNotExistingCommand() { Command command = new Command(); String commandString = "src/test/resources/notExistingScript" + scriptExtension; - CommandResult commandResult = command.runCommand(processId, commandString); + CommandResult commandResult = command.runCommand(commandString); - CommandResult expectedCommandResult = new CommandResult(processId, commandString, false, null); + CommandResult expectedCommandResult = new CommandResult(commandString, false, null); assertEquals("Should not run not existing Command", expectedCommandResult.isSuccessful(), commandResult.isSuccessful()); @@ -108,9 +107,9 @@ public void shouldNotRunCommandWithFalseSyntax() { Command command = new Command(); String commandString = "src/test/resources/not_working_script" + scriptExtension; - CommandResult commandResult = command.runCommand(processId, commandString); + CommandResult commandResult = command.runCommand(commandString); - CommandResult expectedCommandResult = new CommandResult(processId, commandString, false, null); + CommandResult expectedCommandResult = new CommandResult(commandString, false, null); assertEquals("Should not run command with false syntax", expectedCommandResult.isSuccessful(), commandResult.isSuccessful()); @@ -121,7 +120,7 @@ public void shouldRunCommandWithParameter() { Command command = new Command(); String commandString = "src/test/resources/working_script_with_parameters" + scriptExtension + " testParameter"; - CommandResult commandResult = command.runCommand(processId, commandString); + CommandResult commandResult = command.runCommand(commandString); ArrayList expectedMessages = new ArrayList<>(); @@ -132,7 +131,7 @@ public void shouldRunCommandWithParameter() { expectedMessages.add("testParameter"); - CommandResult expectedCommandResult = new CommandResult(processId, commandString, true, expectedMessages); + CommandResult expectedCommandResult = new CommandResult(commandString, true, expectedMessages); assertEquals("successful booleans of CommandResults are not identical", expectedCommandResult.isSuccessful(), commandResult.isSuccessful()); diff --git a/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/entities/PhysicalStructMapType.java b/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/entities/PhysicalStructMapType.java index 1471cd4249b..6ff19803f04 100644 --- a/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/entities/PhysicalStructMapType.java +++ b/Kitodo-DataEditor/src/main/java/org/kitodo/dataeditor/entities/PhysicalStructMapType.java @@ -17,13 +17,13 @@ import java.util.NoSuchElementException; import java.util.Objects; -import org.kitodo.api.dataformat.PhysicalDivision; import org.kitodo.config.KitodoConfig; import org.kitodo.config.enums.ParameterDataEditor; import org.kitodo.dataeditor.MetsKitodoObjectFactory; import org.kitodo.dataformat.metskitodo.DivType; import org.kitodo.dataformat.metskitodo.FileType; import org.kitodo.dataformat.metskitodo.StructMapType; +import org.kitodo.utils.MediaUtil; public class PhysicalStructMapType extends StructMapType { @@ -63,7 +63,7 @@ private List getDivTypesByFileTypes(List fileTypes) { div.setID("PHYS_" + String.format("%04d", counter)); div.setORDER(BigInteger.valueOf(counter)); div.setORDERLABEL(KitodoConfig.getParameter(ParameterDataEditor.METS_EDITOR_DEFAULT_PAGINATION)); - div.setTYPE(getPhysicalDivTypeByFileType(file)); + div.setTYPE(MediaUtil.getPhysicalDivisionTypeOfMimeType(file.getMIMETYPE())); DivType.Fptr divTypeFptr = objectFactory.createDivTypeFptr(); divTypeFptr.setFILEID(file); div.getFptr().add(divTypeFptr); @@ -75,16 +75,6 @@ private List getDivTypesByFileTypes(List fileTypes) { return divTypes; } - private String getPhysicalDivTypeByFileType(FileType file) { - if (file.getMIMETYPE().contains("image")) { - return PhysicalDivision.TYPE_PAGE; - } - if (file.getMIMETYPE().contains("audio")) { - return PhysicalDivision.TYPE_TRACK; - } - return PhysicalDivision.TYPE_OTHER; - } - private DivType getDivById(String id) { for (DivType div : this.getDiv().getDiv()) { if (Objects.equals(div.getID(),id)) { diff --git a/Kitodo-DataEditor/src/test/java/org/kitodo/dataeditor/MetsKitodoWrapperTest.java b/Kitodo-DataEditor/src/test/java/org/kitodo/dataeditor/MetsKitodoWrapperTest.java index 42ab75b63ab..ae38813dee3 100644 --- a/Kitodo-DataEditor/src/test/java/org/kitodo/dataeditor/MetsKitodoWrapperTest.java +++ b/Kitodo-DataEditor/src/test/java/org/kitodo/dataeditor/MetsKitodoWrapperTest.java @@ -36,6 +36,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.kitodo.api.dataformat.PhysicalDivision; import org.kitodo.dataeditor.enums.FileLocationType; import org.kitodo.dataeditor.enums.PositionOfNewDiv; import org.kitodo.dataformat.metskitodo.DivType; @@ -202,22 +203,22 @@ public void shouldReadGoobiMetadataGroup() } @Test - public void shouldInsertFileGroup() throws IOException, DatatypeConfigurationException { + public void shouldInsertFileGroup() throws DatatypeConfigurationException, IOException { Path path = Paths.get("images"); int numberOfFiles = 5; List mediaFiles = new ArrayList<>(); for (int i = 1; i <= numberOfFiles; i++) { mediaFiles.add( - new MediaFile(Paths.get(path + "/0000" + i + ".tif").toUri(), FileLocationType.URL, "image/tiff")); + new MediaFile(Paths.get(path + "/0000" + i + ".tif").toUri(), FileLocationType.URL, "image/tiff")); } MetsKitodoWrapper metsKitodoWrapper = new MetsKitodoWrapper("Manuscript"); metsKitodoWrapper.insertMediaFiles(mediaFiles); Assert.assertEquals("Wrong number of divs in physical structMap", numberOfFiles, - metsKitodoWrapper.getPhysicalStructMap().getDiv().getDiv().size()); - Assert.assertEquals("Wrong number of fils in fileSec", numberOfFiles, - metsKitodoWrapper.getMets().getFileSec().getFileGrp().get(0).getFile().size()); + metsKitodoWrapper.getPhysicalStructMap().getDiv().getDiv().size()); + Assert.assertEquals("Wrong number of files in fileSec", numberOfFiles, + metsKitodoWrapper.getMets().getFileSec().getFileGrp().get(0).getFile().size()); DivType divType = metsKitodoWrapper.getPhysicalStructMap().getDiv().getDiv().get(1); @@ -231,6 +232,28 @@ public void shouldInsertFileGroup() throws IOException, DatatypeConfigurationExc } + + @Test + public void testPhysicalDivisionType() throws IOException, DatatypeConfigurationException { + List mediaFiles = new ArrayList<>(); + mediaFiles.add(new MediaFile(Paths.get("image/jpeg/0001.jpeg").toUri(), FileLocationType.URL, "image/jpeg")); + mediaFiles.add(new MediaFile(Paths.get("audio/mpeg/0002.jpeg").toUri(), FileLocationType.URL, "audio/mpeg")); + mediaFiles.add(new MediaFile(Paths.get("video/mp4/0003.jpeg").toUri(), FileLocationType.URL, "video/mp4")); + + MetsKitodoWrapper metsKitodoWrapper = new MetsKitodoWrapper("MultiMedia"); + metsKitodoWrapper.insertMediaFiles(mediaFiles); + + Assert.assertEquals("Wrong number of divs in physical structMap", 3, + metsKitodoWrapper.getPhysicalStructMap().getDiv().getDiv().size()); + Assert.assertEquals("Wrong number of files in fileSec", 3, + metsKitodoWrapper.getMets().getFileSec().getFileGrp().get(0).getFile().size()); + + List divTypes = metsKitodoWrapper.getPhysicalStructMap().getDiv().getDiv(); + Assert.assertEquals(PhysicalDivision.TYPE_PAGE, divTypes.get(0).getTYPE()); + Assert.assertEquals(PhysicalDivision.TYPE_TRACK, divTypes.get(1).getTYPE()); + Assert.assertEquals(PhysicalDivision.TYPE_TRACK, divTypes.get(2).getTYPE()); + } + @Rule public ExpectedException expectedException = ExpectedException.none(); diff --git a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Process.java b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Process.java index aa706aa4152..dc69926aca4 100644 --- a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Process.java +++ b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Process.java @@ -85,7 +85,7 @@ public class Process extends BaseTemplateBean { @OrderBy("ordering") private List tasks; - @OneToMany(mappedBy = "process", cascade = CascadeType.ALL) + @OneToMany(mappedBy = "process", cascade = CascadeType.PERSIST, orphanRemoval = true) private List comments; @ManyToMany(cascade = CascadeType.ALL) @@ -116,6 +116,9 @@ public class Process extends BaseTemplateBean { @Column(name = "inChoiceListShown") Boolean inChoiceListShown; + @Column(name = "ocrd_workflow_id") + private String ocrdWorkflowId; + @Transient private User blockedUser; @@ -651,4 +654,23 @@ public void setNumberOfImages(int numberOfImages) { public void setNumberOfStructures(int numberOfStructures) { this.numberOfStructures = numberOfStructures; } + + /** + * Get OCR-D workflow identifier. + * + * @return The OCR-D workflow identifier + */ + public String getOcrdWorkflowId() { + return ocrdWorkflowId; + } + + /** + * Set the OCR-D workflow identifier. + * + * @param ocrdWorkflowId + * The identifier of the OCR-D workflow + */ + public void setOcrdWorkflowId(String ocrdWorkflowId) { + this.ocrdWorkflowId = ocrdWorkflowId; + } } diff --git a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Project.java b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Project.java index faf64e45948..138fe59053f 100644 --- a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Project.java +++ b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Project.java @@ -128,19 +128,24 @@ public class Project extends BaseIndexedBean implements Comparable { private Folder preview; /** - * Folder with media to use for the video preview. + * Folder with media to use for the audio preview. */ @ManyToOne @JoinColumn(name = "preview_audio_folder_id", foreignKey = @ForeignKey(name = "FK_project_preview_audio_folder_id")) private Folder audioPreview; /** - * Folder with media to use for the video viewer. + * Folder with media to use for the audio viewer. */ @ManyToOne @JoinColumn(name = "mediaView_audio_folder_id", foreignKey = @ForeignKey(name = "FK_project_mediaView_audio_folder_id")) private Folder audioMediaView; + /** + * Field to define the status of the audio media view waveform. + */ + @Column(name = "mediaView_audio_waveform") + private Boolean audioMediaViewWaveform = false; /** * Folder with media to use for the video preview. @@ -553,6 +558,24 @@ public void setAudioMediaView(Folder audioMediaView) { this.audioMediaView = audioMediaView; } + /** + * Get the status of the audio media view waveform. + * + * @return True if is active + */ + public boolean isAudioMediaViewWaveform() { + return audioMediaViewWaveform; + } + + /** + * Set the status of the audio media view waveform. + * + * @param audioMediaViewWaveform True if is active + */ + public void setAudioMediaViewWaveform(boolean audioMediaViewWaveform) { + this.audioMediaViewWaveform = audioMediaViewWaveform; + } + /** * Returns the folder to use for video preview. * diff --git a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Template.java b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Template.java index 93e997ab8ce..e26c256a697 100644 --- a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Template.java +++ b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/beans/Template.java @@ -65,6 +65,9 @@ public class Template extends BaseTemplateBean { @JoinColumn(name = "project_id", foreignKey = @ForeignKey(name = "FK_project_x_template_project_id")) }) private List projects; + @Column(name = "ocrd_workflow_id") + private String ocrdWorkflowId; + /** * Constructor. */ @@ -168,6 +171,26 @@ public void setWorkflow(Workflow workflow) { this.workflow = workflow; } + + /** + * Get OCR-D workflow identifier. + * + * @return value of OCR-D workflow identifier + */ + public String getOcrdWorkflowId() { + return ocrdWorkflowId; + } + + /** + * Set the OCR-D workflow identifier. + * + * @param ocrdWorkflowId + * The identifier of the OCR-D workflow + */ + public void setOcrdWorkflowId(String ocrdWorkflowId) { + this.ocrdWorkflowId = ocrdWorkflowId; + } + /** * Get projects list. * diff --git a/Kitodo-DataManagement/src/main/resources/db/migration/V2_125__Add_audio_media_view_waveform_to_project.sql b/Kitodo-DataManagement/src/main/resources/db/migration/V2_125__Add_audio_media_view_waveform_to_project.sql new file mode 100644 index 00000000000..3918267acbf --- /dev/null +++ b/Kitodo-DataManagement/src/main/resources/db/migration/V2_125__Add_audio_media_view_waveform_to_project.sql @@ -0,0 +1,14 @@ +-- +-- (c) Kitodo. Key to digital objects e. V. +-- +-- This file is part of the Kitodo project. +-- +-- It is licensed under GNU General Public License version 3 or later. +-- +-- For the full copyright and license information, please read the +-- GPL3-License.txt file that was distributed with this source code. +-- + +-- +-- Migration: Add column for state of audio waveform in media view to project table. +ALTER TABLE project ADD mediaView_audio_waveform TINYINT(1) NOT NULL DEFAULT 0; diff --git a/Kitodo-DataManagement/src/main/resources/db/migration/V2_126__Add_authority_and_fields_for_ocrd_workflows.sql b/Kitodo-DataManagement/src/main/resources/db/migration/V2_126__Add_authority_and_fields_for_ocrd_workflows.sql new file mode 100644 index 00000000000..336275943f2 --- /dev/null +++ b/Kitodo-DataManagement/src/main/resources/db/migration/V2_126__Add_authority_and_fields_for_ocrd_workflows.sql @@ -0,0 +1,17 @@ +-- +-- (c) Kitodo. Key to digital objects e. V. +-- +-- This file is part of the Kitodo project. +-- +-- It is licensed under GNU General Public License version 3 or later. +-- +-- For the full copyright and license information, please read the +-- GPL3-License.txt file that was distributed with this source code. +-- + +-- Add authority to assign OCR-D workflow in template or process details +INSERT IGNORE INTO authority (title) VALUES ('assignOcrdWorkflow_clientAssignable'); + +-- Add columns of OCR-D workflow identifier +ALTER TABLE process ADD ocrd_workflow_id varchar(255) DEFAULT NULL; +ALTER TABLE template ADD ocrd_workflow_id varchar(255) DEFAULT NULL; diff --git a/Kitodo-Docket/src/main/java/org/kitodo/docket/ExportXmlLog.java b/Kitodo-Docket/src/main/java/org/kitodo/docket/ExportXmlLog.java index 735ce9695d7..4e1e560d46b 100644 --- a/Kitodo-Docket/src/main/java/org/kitodo/docket/ExportXmlLog.java +++ b/Kitodo-Docket/src/main/java/org/kitodo/docket/ExportXmlLog.java @@ -300,9 +300,15 @@ private ArrayList processProcessInformation(DocketData docketData, Name ruleset.setText(docketData.getRulesetName()); processElements.add(ruleset); - Element comment = new Element("comment", xmlns); - comment.setText(docketData.getComment()); - processElements.add(comment); + Element comments = new Element("comments", xmlns); + List commentList = new ArrayList<>(); + for (String commentString : docketData.getComments()) { + Element comment = new Element("comment", xmlns); + comment.setText(commentString); + commentList.add(comment); + } + comments.addContent(commentList); + processElements.add(comments); List processProperties = prepareProperties(docketData.getProcessProperties(), xmlns); diff --git a/Kitodo-Docket/src/test/java/org/kitodo/docket/DocketDataGenerator.java b/Kitodo-Docket/src/test/java/org/kitodo/docket/DocketDataGenerator.java index 613c00cb03c..eed145b1584 100644 --- a/Kitodo-Docket/src/test/java/org/kitodo/docket/DocketDataGenerator.java +++ b/Kitodo-Docket/src/test/java/org/kitodo/docket/DocketDataGenerator.java @@ -14,6 +14,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.kitodo.api.docket.DocketData; @@ -32,7 +33,7 @@ public DocketData createDocketData(String processID, String signatur, String doc docketdata.setProcessName("ProcessTitle"); docketdata.setProjectName("projectTitle"); docketdata.setRulesetName("RulesetTitle"); - docketdata.setComment("A comment"); + docketdata.setComments(Collections.singletonList("A comment")); List templateProperties = new ArrayList<>(); Property propertyForDocket = new Property(); diff --git a/Kitodo-Docket/src/test/resources/docket_multipage.xsl b/Kitodo-Docket/src/test/resources/docket_multipage.xsl index ff6c22bdc17..f207a4519ad 100644 --- a/Kitodo-Docket/src/test/resources/docket_multipage.xsl +++ b/Kitodo-Docket/src/test/resources/docket_multipage.xsl @@ -334,13 +334,15 @@ - - - - - - - + + + + + + + + + diff --git a/Kitodo-FileManagement/src/main/java/org/kitodo/filemanagement/CommandService.java b/Kitodo-FileManagement/src/main/java/org/kitodo/filemanagement/CommandService.java index bd015ce934d..ce7c1901517 100644 --- a/Kitodo-FileManagement/src/main/java/org/kitodo/filemanagement/CommandService.java +++ b/Kitodo-FileManagement/src/main/java/org/kitodo/filemanagement/CommandService.java @@ -14,7 +14,6 @@ import java.io.File; import java.io.IOException; import java.util.List; -import java.util.Random; import org.kitodo.api.command.CommandInterface; import org.kitodo.api.command.CommandResult; @@ -22,8 +21,6 @@ class CommandService { - private Random random = new Random(1000000); - /** * Method executes a script string. * @@ -39,7 +36,7 @@ CommandResult runCommand(String script) throws IOException { KitodoServiceLoader serviceLoader = new KitodoServiceLoader<>(CommandInterface.class); CommandInterface command = serviceLoader.loadModule(); - CommandResult commandResult = command.runCommand(random.nextInt(), script); + CommandResult commandResult = command.runCommand(script); List commandResultMessages = commandResult.getMessages(); if (commandResultMessages.size() > 0 && commandResultMessages.get(0).contains("IOException")) { throw new IOException(commandResultMessages.get(1)); diff --git a/Kitodo-FileManagement/src/main/java/org/kitodo/filemanagement/FileManagement.java b/Kitodo-FileManagement/src/main/java/org/kitodo/filemanagement/FileManagement.java index 0a2c8702e2f..2540560fcbb 100644 --- a/Kitodo-FileManagement/src/main/java/org/kitodo/filemanagement/FileManagement.java +++ b/Kitodo-FileManagement/src/main/java/org/kitodo/filemanagement/FileManagement.java @@ -393,7 +393,7 @@ public URI createProcessLocation(String processId) throws IOException { File processRootDirectory = new File(KitodoConfig.getKitodoDataDirectory() + File.separator + processId); String scriptCreateDirMeta = KitodoConfig.getParameter("script_createDirMeta"); String command = scriptCreateDirMeta + ' ' + processRootDirectory.getPath(); - if (!processRootDirectory.exists() && !commandService.runCommand(command.hashCode(), command).isSuccessful()) { + if (!processRootDirectory.exists() && !commandService.runCommand(command).isSuccessful()) { throw new IOException("Could not create processRoot directory."); } return fileMapper.unmapUriFromKitodoDataDirectoryUri(Paths.get(processRootDirectory.getPath()).toUri()); diff --git a/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java b/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java index 755ac4b7bf7..70fee5f5879 100644 --- a/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java +++ b/Kitodo/src/main/java/org/kitodo/config/enums/ParameterCore.java @@ -46,6 +46,12 @@ public enum ParameterCore implements ParameterInterface { */ DIR_RULESETS(new Parameter("directory.rulesets")), + /** + * Absolute path to the directory that the OCR-D workflow files will be + * read from. It must be terminated by a directory separator ("/"). + */ + OCRD_WORKFLOWS_DIR(new Parameter<>("ocrd.workflows.directory", "")), + /** * Absolute path to the directory that XSLT files are stored in which are used * to transform the "XML log" (as visible from the XML button in the processes @@ -639,8 +645,12 @@ public enum ParameterCore implements ParameterInterface { * Secret is used to encrypt or decrypt LDAP manager passwords which are stored in the database in encrypted form. * Once the secret value is set, it should not be changed since encrypted data can no longer be decrypted. */ - SECURITY_SECRET_LDAPMANAGERPASSWORD(new Parameter<>("security.secret.ldapManagerPassword", "")); + SECURITY_SECRET_LDAPMANAGERPASSWORD(new Parameter<>("security.secret.ldapManagerPassword", "")), + /* Optional parameter can be used to limit the number of processes for which media renaming can be conducted as a + * list function. Values different from positive integers are interpreted as "unlimited". + */ + MAX_NUMBER_OF_PROCESSES_FOR_MEDIA_RENAMING(new Parameter<>("maxNumberOfProcessesForMediaRenaming", -1)); private final Parameter parameter; diff --git a/Kitodo/src/main/java/org/kitodo/production/controller/SecurityAccessController.java b/Kitodo/src/main/java/org/kitodo/production/controller/SecurityAccessController.java index 032f46ef4ba..72eb057e8e4 100644 --- a/Kitodo/src/main/java/org/kitodo/production/controller/SecurityAccessController.java +++ b/Kitodo/src/main/java/org/kitodo/production/controller/SecurityAccessController.java @@ -222,6 +222,15 @@ public boolean hasAuthorityToAddOnProjectPage() { return securityAccessService.hasAuthorityToAddOnProjectPage(); } + /** + * Check if the current user has the authority to add OCR-D workflow. + * + * @return true if the current user has the authority to add OCR-D workflow. + */ + public boolean hasAuthorityToAssignOcrdWorkflow() { + return securityAccessService.hasAuthorityToAssignOcrdWorkflow(); + } + /** * Check if the current user has the authority to delete the batch. * diff --git a/Kitodo/src/main/java/org/kitodo/production/controller/SessionClientController.java b/Kitodo/src/main/java/org/kitodo/production/controller/SessionClientController.java index 1aba07941d4..38d1e832539 100644 --- a/Kitodo/src/main/java/org/kitodo/production/controller/SessionClientController.java +++ b/Kitodo/src/main/java/org/kitodo/production/controller/SessionClientController.java @@ -15,6 +15,7 @@ import java.util.Objects; import javax.enterprise.context.RequestScoped; +import javax.faces.context.FacesContext; import javax.inject.Named; import org.kitodo.data.database.beans.Client; @@ -134,8 +135,12 @@ public void setSelectedClient(Client selectedClient) { * @param sessionClient * The client object that is to be the new session client. */ - public void setSessionClient(Client sessionClient) { + public String setSessionClient(Client sessionClient) { ServiceManager.getUserService().getAuthenticatedUser().setSessionClient(sessionClient); + if (Objects.nonNull(FacesContext.getCurrentInstance())) { + return FacesContext.getCurrentInstance().getViewRoot().getViewId(); + } + return null; } /** diff --git a/Kitodo/src/main/java/org/kitodo/production/converter/OcrdWorkflowConverter.java b/Kitodo/src/main/java/org/kitodo/production/converter/OcrdWorkflowConverter.java new file mode 100644 index 00000000000..4070df06cf5 --- /dev/null +++ b/Kitodo/src/main/java/org/kitodo/production/converter/OcrdWorkflowConverter.java @@ -0,0 +1,45 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * This file is part of the Kitodo project. + * + * It is licensed under GNU General Public License version 3 or later. + * + * For the full copyright and license information, please read the + * GPL3-License.txt file that was distributed with this source code. + */ + +package org.kitodo.production.converter; + +import java.util.Objects; + +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.convert.Converter; +import javax.inject.Named; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.kitodo.production.services.ServiceManager; + + +@Named +public class OcrdWorkflowConverter extends BeanConverter implements Converter { + + @Override + public Object getAsObject(FacesContext context, UIComponent component, String value) { + if (StringUtils.isNotEmpty(value)) { + return ServiceManager.getOcrdWorkflowService().getOcrdWorkflow(value); + } + return null; + } + + @Override + public String getAsString(FacesContext context, UIComponent component, Object value) { + if (Objects.nonNull(value) && value instanceof Pair) { + return (String) ((Pair) value).getKey(); + } + return null; + } + +} diff --git a/Kitodo/src/main/java/org/kitodo/production/enums/ObjectType.java b/Kitodo/src/main/java/org/kitodo/production/enums/ObjectType.java index 95b03001ba9..8e7485f925c 100644 --- a/Kitodo/src/main/java/org/kitodo/production/enums/ObjectType.java +++ b/Kitodo/src/main/java/org/kitodo/production/enums/ObjectType.java @@ -37,6 +37,7 @@ public enum ObjectType { FILTER("filter", "filters", true), IMPORT_CONFIGURATION("importConfig.configuration", "importConfig.configurations", false), MAPPING_FILE("mappingFile.file", "mappingFile.files", false), + COMMENT("comment", "comments", false), NONE("", "", false); private final String messageKeySingular; diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/CommentForm.java b/Kitodo/src/main/java/org/kitodo/production/forms/CommentForm.java index 05e8ebf9c20..c1ffe7190d5 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/CommentForm.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/CommentForm.java @@ -44,6 +44,7 @@ public class CommentForm extends BaseForm { private static final Logger logger = LogManager.getLogger(CommentForm.class); private boolean correctionComment = false; + private Comment editedComment = null; private String commentMessage; private String correctionTaskId; private Task currentTask; @@ -60,6 +61,21 @@ public Process getProcess() { return process; } + /** + * Remove a given comment. + * + * @param comment to be removed. + */ + public void removeComment(Comment comment) { + try { + ServiceManager.getCommentService().removeComment(comment); + saveProcessAndTasksToIndex(); + } catch (CustomResponseException | DAOException | DataException | IOException e) { + Helper.setErrorMessage(ERROR_DELETING, new Object[]{ObjectType.COMMENT.getTranslationSingular()}, + logger, e); + } + } + /** * Set process by ID. * @@ -103,6 +119,32 @@ public void setCommentMessage(String commentMessage) { this.commentMessage = commentMessage; } + /** + * Set's edited comment. + * + * @param comment to be set as editedComment. + */ + public void setEditedComment(Comment comment) { + this.editedComment = comment; + } + + /** + * Returns edited comment. + * + * @return edited comment. + */ + public Comment getEditedComment() { + return this.editedComment; + } + + private void saveProcessAndTasksToIndex() throws CustomResponseException, DataException, IOException { + ServiceManager.getProcessService().saveToIndex(this.process, true); + for (Task task : this.process.getTasks()) { + // update tasks in elastic search index, which includes correction comment status + ServiceManager.getTaskService().saveToIndex(task, true); + } + } + /** * Add a new comment to the process. */ @@ -128,12 +170,8 @@ public String addComment() { } try { ServiceManager.getCommentService().saveToDatabase(comment); - ServiceManager.getProcessService().saveToIndex(this.process, true); - for (Task task : this.process.getTasks()) { - // update tasks in elastic search index, which includes correction comment status - ServiceManager.getTaskService().saveToIndex(task, true); - } - } catch (DAOException | CustomResponseException | DataException | IOException e) { + saveProcessAndTasksToIndex(); + } catch (CustomResponseException | DAOException | DataException | IOException e) { Helper.setErrorMessage(ERROR_SAVING, logger, e); } newComment(false); @@ -144,6 +182,21 @@ public String addComment() { return null; } + /** + * Saves the edited comment to database. + */ + public void saveEditedComment() { + if (Objects.nonNull(this.editedComment) && this.editedComment.getType().equals(CommentType.INFO)) { + try { + ServiceManager.getCommentService().saveToDatabase(this.editedComment); + saveProcessAndTasksToIndex(); + } catch (CustomResponseException | DAOException | DataException | IOException e) { + Helper.setErrorMessage(ERROR_SAVING, logger, e); + } + } + this.editedComment = null; + } + /** * Add a new comment to all batch processes. */ diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/ProcessForm.java b/Kitodo/src/main/java/org/kitodo/production/forms/ProcessForm.java index 18cb425973e..0bd56c0dcbf 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/ProcessForm.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/ProcessForm.java @@ -30,6 +30,8 @@ import javax.inject.Inject; import javax.inject.Named; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.kitodo.config.ConfigCore; @@ -61,6 +63,7 @@ import org.kitodo.production.services.data.ProcessService; import org.kitodo.production.services.file.FileService; import org.kitodo.production.services.workflow.WorkflowControllerService; +import org.omnifaces.util.Ajax; import org.primefaces.PrimeFaces; import org.primefaces.event.SelectEvent; import org.primefaces.event.ToggleSelectEvent; @@ -89,6 +92,7 @@ public class ProcessForm extends TemplateBaseForm { private String processEditReferer = DEFAULT_LINK; private String taskEditReferer = DEFAULT_LINK; + private String errorMessage = ""; private List customColumns; @@ -874,6 +878,47 @@ public List getProjects() { return ServiceManager.getProjectService().getAllForSelectedClient(); } + /** + * Get list of OCR-D workflows for select list. + * + * @return list of OCR-D workflows + */ + public List> getOcrdWorkflows() { + return ServiceManager.getOcrdWorkflowService().getOcrdWorkflows(); + } + + /** + * Get the OCR-D workflow. + * + * @return Immutable key value pair + */ + public Pair getOcrdWorkflow() { + return ServiceManager.getOcrdWorkflowService().getOcrdWorkflow(process.getOcrdWorkflowId()); + } + + /** + * Get the OCR-D workflow of process template. + * + * @return Immutable key value pair + */ + public Pair getOcrdWorkflowOfTemplate() { + return ServiceManager.getOcrdWorkflowService().getOcrdWorkflow(process.getTemplate().getOcrdWorkflowId()); + } + + /** + * Set the OCR-D workflow. + * + * @param ocrdWorkflow + * The immutable key value pair + */ + public void setOcrdWorkflow(Pair ocrdWorkflow) { + String ocrdWorkflowId = StringUtils.EMPTY; + if (Objects.nonNull(ocrdWorkflow)) { + ocrdWorkflowId = ocrdWorkflow.getKey().toString(); + } + process.setOcrdWorkflowId(ocrdWorkflowId); + } + /** * Get rulesets for select list. * @@ -1182,4 +1227,46 @@ private int getProcessId(Object process) { public FilterMenu getFilterMenu() { return filterMenu; } + + /** + * Rename media files of all selected processes. + */ + public void renameMedia() { + List processes = getSelectedProcesses(); + errorMessage = ServiceManager.getFileService().tooManyProcessesSelectedForMediaRenaming(processes.size()); + if (StringUtils.isBlank(errorMessage)) { + PrimeFaces.current().executeScript("PF('renameMediaConfirmDialog').show();"); + } else { + Ajax.update("errorDialog"); + PrimeFaces.current().executeScript("PF('errorDialog').show();"); + } + } + + /** + * Start renaming media files of selected processes. + */ + public void startRenaming() { + ServiceManager.getFileService().renameMedia(getSelectedProcesses()); + PrimeFaces.current().executeScript("PF('notifications').renderMessage({'summary':'" + + Helper.getTranslation("renamingMediaFilesOfSelectedProcessesStarted") + + "','severity':'info'})"); + } + + /** + * Return media renaming confirmation message with number of processes affected. + * + * @return media renaming confirmation message + */ + public String getMediaRenamingConfirmMessage() { + return Helper.getTranslation("renameMediaForProcessesConfirmMessage", + String.valueOf(getSelectedProcesses().size())); + } + + /** + * Get error message. + * @return error message + */ + public String getErrorMessage() { + return errorMessage; + } } diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/ProjectForm.java b/Kitodo/src/main/java/org/kitodo/production/forms/ProjectForm.java index d03d39cb624..3a4011f548d 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/ProjectForm.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/ProjectForm.java @@ -674,6 +674,25 @@ public void setAudioMediaView(String audioMediaView) { project.setAudioMediaView(getFolderMap().get(audioMediaView)); } + /** + * Returns the state of the audio media view waveform. + * + * @return True if enabled + */ + public boolean isAudioMediaViewWaveform() { + return project.isAudioMediaViewWaveform(); + } + + /** + * Sets the state of the audio media view waveform. + * + * @param audioMediaViewWaveform True if enabled + * + */ + public void setAudioMediaViewWaveform(boolean audioMediaViewWaveform) { + project.setAudioMediaViewWaveform(audioMediaViewWaveform); + } + /** * Returns the folder to use for the video media view. * diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/TemplateForm.java b/Kitodo/src/main/java/org/kitodo/production/forms/TemplateForm.java index 2af30725d4d..e852d31f89b 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/TemplateForm.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/TemplateForm.java @@ -24,6 +24,7 @@ import javax.inject.Named; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.kitodo.data.database.beans.Docket; @@ -274,6 +275,38 @@ public List getWorkflows() { return ServiceManager.getWorkflowService().getAvailableWorkflows(); } + /** + * Get list of OCR-D workflows for select list. + * + * @return list of OCR-D workflows + */ + public List> getOcrdWorkflows() { + return ServiceManager.getOcrdWorkflowService().getOcrdWorkflows(); + } + + /** + * Get the OCR-D workflow. + * + * @return Immutable key value pair + */ + public Pair getOcrdWorkflow() { + return ServiceManager.getOcrdWorkflowService().getOcrdWorkflow(template.getOcrdWorkflowId()); + } + + /** + * Set the OCR-D workflow. + * + * @param ocrdWorkflow + * The immutable key value pair + */ + public void setOcrdWorkflow(Pair ocrdWorkflow) { + String ocrdWorkflowId = StringUtils.EMPTY; + if (Objects.nonNull(ocrdWorkflow)) { + ocrdWorkflowId = ocrdWorkflow.getKey().toString(); + } + template.setOcrdWorkflowId(ocrdWorkflowId); + } + /** * Check if user is not assigned to the project. Used for disabling projects. * diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/createprocess/SelectProjectDialogView.java b/Kitodo/src/main/java/org/kitodo/production/forms/createprocess/SelectProjectDialogView.java index 5f0e37652e3..97f59cf7800 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/createprocess/SelectProjectDialogView.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/createprocess/SelectProjectDialogView.java @@ -14,7 +14,9 @@ import java.io.IOException; import java.io.Serializable; import java.util.Collections; +import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; import javax.faces.context.FacesContext; import javax.faces.view.ViewScoped; @@ -85,7 +87,8 @@ public void setSelectedProjectId(int projectId) { public List getTemplateProjects() { try { Template template = ServiceManager.getTemplateService().getById(this.templateDTO.getId()); - return template.getProjects(); + return template.getProjects().stream().sorted(Comparator.comparing(Project::getTitle)) + .collect(Collectors.toList()); } catch (DAOException e) { Helper.setErrorMessage(ERROR_LOADING_ONE, new Object[] {ObjectType.TEMPLATE.getTranslationSingular(), this.templateDTO.getId()}, logger, e); diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/AddDocStrucTypeDialog.java b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/AddDocStrucTypeDialog.java index 17eaf77760a..1b0db56b099 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/AddDocStrucTypeDialog.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/AddDocStrucTypeDialog.java @@ -553,7 +553,7 @@ public void prepareAddableMetadataForStructure(boolean currentElement) { } private void prepareSelectPageOnAddNodeItems() { - List physicalDivisions = dataEditor.getWorkpiece().getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted(); + List physicalDivisions = dataEditor.getWorkpiece().getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack(); selectPageOnAddNodeItems = new ArrayList<>(physicalDivisions.size()); for (int i = 0; i < physicalDivisions.size(); i++) { View view = View.of(physicalDivisions.get(i)); diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/EditPagesDialog.java b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/EditPagesDialog.java index 4f35efcdeae..901c17d0a06 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/EditPagesDialog.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/EditPagesDialog.java @@ -206,7 +206,7 @@ List getViewsToAdd(int firstPage, int lastPage) { private List getViewsToAdd(List pages) { return pages.parallelStream() - .map(dataEditor.getWorkpiece().getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted()::get) + .map(dataEditor.getWorkpiece().getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack()::get) .map(MetadataEditor::getFirstViewForPhysicalDivision) .collect(Collectors.toList()); } @@ -232,7 +232,7 @@ void prepare() { paginationSubSelectionItems = new ArrayList<>(); paginationSelectionItems = new ArrayList<>(); - List physicalDivisions = dataEditor.getWorkpiece().getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted(); + List physicalDivisions = dataEditor.getWorkpiece().getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack(); int capacity = (int) Math.ceil(physicalDivisions.size() / .75); Set assigneds = new HashSet<>(capacity); Set unassigneds = new HashSet<>(capacity); diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/GalleryPanel.java b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/GalleryPanel.java index 9818611bfb6..0e764c8fca6 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/GalleryPanel.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/GalleryPanel.java @@ -50,6 +50,7 @@ import org.kitodo.production.model.Subfolder; import org.kitodo.production.services.ServiceManager; import org.kitodo.production.services.file.FileService; +import org.kitodo.utils.MediaUtil; import org.primefaces.PrimeFaces; /** @@ -228,6 +229,13 @@ public void onPageDrop() { } } + /** + * Check if audio media view waveform is activated in project. + */ + public boolean isAudioMediaViewWaveform() { + return dataEditor.getProcess().getProject().isAudioMediaViewWaveform(); + } + private boolean dragStripeIndexMatches(String dragId) { Matcher dragStripeImageMatcher = DRAG_STRIPE_IMAGE.matcher(dragId); Matcher dragUnstructuredMediaMatcher = DRAG_UNSTRUCTURED_MEDIA.matcher(dragId); @@ -349,7 +357,7 @@ void show() { Process process = dataEditor.getProcess(); Project project = process.getProject(); List physicalDivisions = dataEditor.getWorkpiece() - .getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted(); + .getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack(); mediaContentTypeVariants.clear(); mediaContentTypePreviewFolder.clear(); @@ -393,7 +401,7 @@ private void initMediaContentType(List physicalDivisions, Fold */ private void updateMedia() { List physicalDivisions = dataEditor.getWorkpiece() - .getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted(); + .getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack(); medias = new ArrayList<>(physicalDivisions.size()); dataEditor.getMediaProvider().resetMediaResolverForProcess(dataEditor.getProcess().getId()); for (PhysicalDivision physicalDivision : physicalDivisions) { @@ -799,7 +807,7 @@ public boolean isSelected(GalleryMediaContent galleryMediaContent, GalleryStripe private void selectMedia(String physicalDivisionOrder, String stripeIndex, String selectionType) { PhysicalDivision selectedPhysicalDivision = null; for (PhysicalDivision physicalDivision : this.dataEditor.getWorkpiece() - .getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted()) { + .getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack()) { if (Objects.equals(physicalDivision.getOrder(), Integer.parseInt(physicalDivisionOrder))) { selectedPhysicalDivision = physicalDivision; break; @@ -829,7 +837,8 @@ private void selectMedia(String physicalDivisionOrder, String stripeIndex, Strin String scrollScripts = "scrollToSelectedTreeNode();scrollToSelectedPaginationRow();"; if (GalleryViewMode.PREVIEW.equals(galleryViewMode)) { - PrimeFaces.current().executeScript("checkScrollPosition();initializeImage();" + scrollScripts); + PrimeFaces.current().executeScript( + "checkScrollPosition();initializeImage();metadataEditor.gallery.mediaView.update();" + scrollScripts); } else { PrimeFaces.current().executeScript(scrollScripts); } @@ -976,4 +985,32 @@ public int getSeveralAssignmentsIndex(GalleryMediaContent galleryMediaContent) { public String getCachingUUID() { return cachingUUID; } + + /** + * Check if media view has mime type prefix. + * + * @param mimeTypePrefix + * The mime type prefix + * @return True if media view has mime type prefix + */ + public boolean hasMediaViewMimeTypePrefix(String mimeTypePrefix) { + Pair lastSelection = getLastSelection(); + if (Objects.nonNull(lastSelection)) { + GalleryMediaContent galleryMediaContent = getGalleryMediaContent(lastSelection.getKey()); + if (Objects.nonNull(galleryMediaContent)) { + String mediaViewMimeType = galleryMediaContent.getMediaViewMimeType(); + switch (mimeTypePrefix) { + case MediaUtil.MIME_TYPE_AUDIO_PREFIX: + return MediaUtil.isAudio(mediaViewMimeType); + case MediaUtil.MIME_TYPE_VIDEO_PREFIX: + return MediaUtil.isVideo(mediaViewMimeType); + case MediaUtil.MIME_TYPE_IMAGE_PREFIX: + return MediaUtil.isImage(mediaViewMimeType); + default: + } + } + } + return false; + } + } diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/PaginationPanel.java b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/PaginationPanel.java index 50a34a6d63b..12756c817b9 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/PaginationPanel.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/PaginationPanel.java @@ -84,7 +84,7 @@ public void updateMediaReferences() { Helper.setWarnMessage(e.getMessage()); } dataEditor.setMediaUpdated(mediaReferencesChanged); - List physicalDivisions = dataEditor.getWorkpiece().getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted(); + List physicalDivisions = dataEditor.getWorkpiece().getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack(); for (int i = 1; i < physicalDivisions.size(); i++) { PhysicalDivision physicalDivision = physicalDivisions.get(i - 1); physicalDivision.setOrder(i); @@ -112,7 +112,7 @@ public List getPaginationSelectionSelectedItems() { * selected items to set */ public void setPaginationSelectionSelectedItems(List selectedItems) { - List physicalDivisions = dataEditor.getWorkpiece().getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted(); + List physicalDivisions = dataEditor.getWorkpiece().getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack(); if (!selectedItems.isEmpty()) { int lastItemIndex = selectedItems.get(selectedItems.size() - 1); if (this.paginationSelectionSelectedItems.isEmpty() @@ -266,7 +266,7 @@ public void setFictitiousCheckboxChecked(boolean fictitiousCheckboxChecked) { } private void preparePaginationSelectionItems() { - List physicalDivisions = dataEditor.getWorkpiece().getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted(); + List physicalDivisions = dataEditor.getWorkpiece().getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack(); paginationSelectionItems = new ArrayList<>(physicalDivisions.size()); for (int i = 0; i < physicalDivisions.size(); i++) { View view = View.of(physicalDivisions.get(i)); @@ -280,7 +280,7 @@ private void preparePaginationSelectionItems() { */ public void preparePaginationSelectionSelectedItems() { paginationSelectionSelectedItems = new ArrayList<>(); - List physicalDivisions = dataEditor.getWorkpiece().getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted(); + List physicalDivisions = dataEditor.getWorkpiece().getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack(); for (Pair selectedElement : dataEditor.getSelectedMedia()) { for (int i = 0; i < physicalDivisions.size(); i++) { PhysicalDivision physicalDivision = physicalDivisions.get(i); @@ -342,7 +342,7 @@ public void startPaginationClick() { paginationStartValue, fictitiousCheckboxChecked, pageSeparators.get(0).getSeparatorString()); Paginator paginator = new Paginator(initializer); List physicalDivisions = dataEditor.getWorkpiece() - .getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted(); + .getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack(); if (selectPaginationScopeSelectedItem) { for (int i = paginationSelectionSelectedItems.get(0); i < physicalDivisions.size(); i++) { physicalDivisions.get(i).setOrderlabel(paginator.next()); diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/StructurePanel.java b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/StructurePanel.java index d5bcbb5e67c..558751b89b7 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/StructurePanel.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/StructurePanel.java @@ -239,7 +239,7 @@ void deleteSelectedPhysicalDivision() { } } int i = 1; - for (PhysicalDivision physicalDivision : dataEditor.getWorkpiece().getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted()) { + for (PhysicalDivision physicalDivision : dataEditor.getWorkpiece().getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack()) { physicalDivision.setOrder(i); i++; } diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/UploadFileDialog.java b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/UploadFileDialog.java index 47a409d223a..b269b1ec2b1 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/UploadFileDialog.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/UploadFileDialog.java @@ -55,6 +55,7 @@ import org.kitodo.production.services.file.SubfolderFactoryService; import org.kitodo.production.services.image.ImageGenerator; import org.kitodo.production.thread.TaskImageGeneratorThread; +import org.kitodo.utils.MediaUtil; import org.primefaces.PrimeFaces; import org.primefaces.event.FileUploadEvent; import org.primefaces.model.TreeNode; @@ -213,16 +214,6 @@ private MediaVariant getMediaVariant() { return mediaVariant; } - private String getPhysicalDivType() { - if (mimeType.contains("image")) { - return PhysicalDivision.TYPE_PAGE; - } - if (mimeType.contains("audio")) { - return PhysicalDivision.TYPE_TRACK; - } - return PhysicalDivision.TYPE_OTHER; - } - private boolean setUpFolders() { VariableReplacer variableReplacer = new VariableReplacer(null, dataEditor.getProcess(), null); sourceFolder = dataEditor.getProcess().getProject().getGeneratorSource(); @@ -336,7 +327,7 @@ public void prepare() { public void uploadMedia(FileUploadEvent event) { if (event.getFile() != null) { - PhysicalDivision physicalDivision = MetadataEditor.addPhysicalDivision(getPhysicalDivType(), + PhysicalDivision physicalDivision = MetadataEditor.addPhysicalDivision(MediaUtil.getPhysicalDivisionTypeOfMimeType(mimeType), dataEditor.getWorkpiece(), dataEditor.getWorkpiece().getPhysicalStructure(), InsertionPosition.LAST_CHILD_OF_CURRENT_ELEMENT); uploadFileUri = new File(sourceFolderURI.getPath().concat(event.getFile().getFileName())).toURI(); diff --git a/Kitodo/src/main/java/org/kitodo/production/helper/VariableReplacer.java b/Kitodo/src/main/java/org/kitodo/production/helper/VariableReplacer.java index 43571f8e4f8..5a41273c9dd 100644 --- a/Kitodo/src/main/java/org/kitodo/production/helper/VariableReplacer.java +++ b/Kitodo/src/main/java/org/kitodo/production/helper/VariableReplacer.java @@ -21,6 +21,7 @@ import java.util.regex.Pattern; import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.kitodo.api.dataformat.LogicalDivision; @@ -70,10 +71,9 @@ private enum MetadataLevel { * be replaced. */ private static final Pattern VARIABLE_FINDER_REGEX = Pattern.compile( - "(\\$?)\\((?:(prefs|processid|processtitle|projectid|stepid|stepname|generatorsource|generatorsourcepath)|" - + "(?:(meta|process|product|template)\\.(?:(firstchild|topstruct)\\.)?([^)]+)|" - + "(?:(filename|basename|relativepath))))\\)"); - + "(\\$?)\\((?:(prefs|processid|processtitle|projectid|stepid|stepname|generatorsource|generatorsourcepath|ocrdworkflowid)|" + + "(?:(meta|process|product|template)\\.(?:(firstchild|topstruct)\\.)?([^)]+)|" + + "(?:(filename|basename|relativepath))))\\)"); /** * The map is filled with replacement instructions that are required for * backwards compatibility with version 2. @@ -244,6 +244,8 @@ private String determineReplacementForInternalValue(Matcher variableFinder) { case "generatorsource" : case "generatorsourcepath": return determineReplacementForGeneratorSource(variableFinder, variableFinder.group(2)); + case "ocrdworkflowid": + return determineReplacementForOcrdWorkflowId(variableFinder); default: logger.warn("Cannot replace \"{}\": no such case defined in switch", variableFinder.group()); return variableFinder.group(); @@ -281,6 +283,28 @@ private String determineReplacementForProcessid(Matcher variableFinder) { return variableFinder.group(1) + process.getId().toString(); } + private String determineReplacementForOcrdWorkflowId(Matcher variableFinder) { + if (Objects.isNull(process)) { + logger.warn("Cannot replace \"(ocrdworkflowid)\": no process given"); + return variableFinder.group(1); + } + + if (StringUtils.isNotEmpty(process.getOcrdWorkflowId())) { + return variableFinder.group(1) + process.getOcrdWorkflowId(); + } + + if (Objects.isNull(process.getTemplate())) { + logger.warn("Cannot replace \"(ocrdworkflowid)\": process has no template assigned"); + return variableFinder.group(1); + } + + if (StringUtils.isEmpty(process.getTemplate().getOcrdWorkflowId())) { + logger.warn("Cannot replace \"(ocrdworkflowid)\": template has no OCR-D workflow assigned"); + return variableFinder.group(1); + } + return variableFinder.group(1) + process.getTemplate().getOcrdWorkflowId(); + } + private String determineReplacementForProcesstitle(Matcher variableFinder) { if (Objects.isNull(process)) { logger.warn("Cannot replace \"(processtitle)\": no process given"); @@ -368,9 +392,8 @@ private String determineReplacementForMetadata(Matcher variableFinder) { switch (metadataLevel) { case ALL: - List allChildren = workpiece.getLogicalStructure().getChildren(); - String allFirstchildValue = allChildren.isEmpty() ? null - : MetadataEditor.getMetadataValue(allChildren.get(0), variableFinder.group(5)); + String allFirstchildValue = determinateReplacementForAll(variableFinder, dollarSignIfToKeep); + if (Objects.nonNull(allFirstchildValue)) { return allFirstchildValue; } @@ -387,6 +410,24 @@ private String determineReplacementForMetadata(Matcher variableFinder) { } } + private String determinateReplacementForAll(Matcher variableFinder, String dollarSignIfToKeep) { + List allChildren = workpiece.getLogicalStructure().getChildren(); + String allFirstchildValue = null; + if (!allChildren.isEmpty()) { + allFirstchildValue = MetadataEditor.getMetadataValue(allChildren.get(0), variableFinder.group(5)); + if (Objects.isNull(allFirstchildValue)) { + allFirstchildValue = determineReplacementForTopstruct(variableFinder, dollarSignIfToKeep); + } + if (StringUtils.isEmpty(allFirstchildValue)) { + List firstChildChildren = allChildren.get(0).getChildren(); + if (!firstChildChildren.isEmpty()) { + allFirstchildValue = MetadataEditor.getMetadataValue(firstChildChildren.get(0), variableFinder.group(5)); + } + } + } + return allFirstchildValue; + } + private String determineReplacementForTopstruct(Matcher variableFinder, String failureResult) { String value = MetadataEditor.getMetadataValue(workpiece.getLogicalStructure(), variableFinder.group(5)); if (Objects.isNull(value)) { diff --git a/Kitodo/src/main/java/org/kitodo/production/services/ServiceManager.java b/Kitodo/src/main/java/org/kitodo/production/services/ServiceManager.java index 10bbe24abe1..30c82187d79 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/ServiceManager.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/ServiceManager.java @@ -48,6 +48,7 @@ import org.kitodo.production.services.image.ImageService; import org.kitodo.production.services.index.IndexingService; import org.kitodo.production.services.migration.MigrationService; +import org.kitodo.production.services.ocr.OcrdWorkflowService; import org.kitodo.production.services.schema.SchemaService; import org.kitodo.production.services.security.SecurityAccessService; import org.kitodo.production.services.security.SessionService; @@ -83,6 +84,7 @@ public class ServiceManager { private static MetsService metsService; private static MigrationService migrationService; private static ImportConfigurationService importConfigurationService; + private static OcrdWorkflowService ocrdWorkflowService; private static PropertyService propertyService; private static ProcessService processService; private static ProjectService projectService; @@ -201,6 +203,12 @@ private static void initializeFolderService() { } } + private static void initializeOcrdWorkflowService() { + if (Objects.isNull(ocrdWorkflowService)) { + ocrdWorkflowService = OcrdWorkflowService.getInstance(); + } + } + private static void initializeProjectService() { if (Objects.isNull(projectService)) { projectService = ProjectService.getInstance(); @@ -507,6 +515,17 @@ public static FolderService getFolderService() { return folderService; } + /** + * Initialize OcrdWorkflowService if it is not yet initialized and next return + * it. + * + * @return OcrdWorkflowService object + */ + public static OcrdWorkflowService getOcrdWorkflowService() { + initializeOcrdWorkflowService(); + return ocrdWorkflowService; + } + /** * Initialize ProjectService if it is not yet initialized and next return * it. diff --git a/Kitodo/src/main/java/org/kitodo/production/services/command/CommandService.java b/Kitodo/src/main/java/org/kitodo/production/services/command/CommandService.java index 4ce4a48a962..9d67eaf1571 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/command/CommandService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/command/CommandService.java @@ -19,17 +19,14 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.Random; import org.kitodo.api.command.CommandInterface; import org.kitodo.api.command.CommandResult; -import org.kitodo.production.services.ServiceManager; import org.kitodo.serviceloader.KitodoServiceLoader; public class CommandService { private final CommandInterface commandModule; private final ArrayList finishedCommandResults = new ArrayList<>(); - private final Random random = new Random(1000000); /** * Initialize Command Service. @@ -54,7 +51,7 @@ public CommandResult runCommand(String script) throws IOException { if (Objects.isNull(script)) { return null; } - CommandResult commandResult = commandModule.runCommand(random.nextInt(), script); + CommandResult commandResult = commandModule.runCommand(script); List commandResultMessages = commandResult.getMessages(); if (!commandResultMessages.isEmpty() && commandResultMessages.get(0).contains("IOException")) { throw new IOException(commandResultMessages.get(1)); @@ -110,7 +107,7 @@ public CommandResult runCommand(File scriptFile) throws IOException { public void runCommandAsync(String script) { if (Objects.nonNull(script)) { Flowable source = Flowable.fromCallable(() -> - commandModule.runCommand(random.nextInt(), script) + commandModule.runCommand(script) ); Flowable commandBackgroundWorker = source.subscribeOn(Schedulers.io()); diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/CommentService.java b/Kitodo/src/main/java/org/kitodo/production/services/data/CommentService.java index 63b1470591b..66331f94b76 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/CommentService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/CommentService.java @@ -20,6 +20,7 @@ import org.kitodo.data.database.beans.Task; import org.kitodo.data.database.exceptions.DAOException; import org.kitodo.data.database.persistence.CommentDAO; +import org.kitodo.production.services.ServiceManager; import org.kitodo.production.services.data.base.SearchDatabaseService; import org.primefaces.model.SortOrder; @@ -91,4 +92,14 @@ public List getAllCommentsByTask(Task task) { public void saveList(List list) throws DAOException { dao.saveList(list); } + + /** + * Remove comment from database and resolve associations. + * + * @param comment to be removed. + */ + public void removeComment(Comment comment) throws DAOException { + comment.getProcess().getComments().remove(comment); + ServiceManager.getCommentService().removeFromDatabase(comment); + } } diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/ProcessService.java b/Kitodo/src/main/java/org/kitodo/production/services/data/ProcessService.java index 210d3f9a20b..18e5342ea4f 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/ProcessService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/ProcessService.java @@ -37,6 +37,8 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.time.Duration; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -2036,7 +2038,7 @@ private static DocketData getDocketData(Process process) throws IOException { docketdata.setProcessName(process.getTitle()); docketdata.setProjectName(process.getProject().getTitle()); docketdata.setRulesetName(process.getRuleset().getTitle()); - docketdata.setComment(process.getWikiField()); + docketdata.setComments(getDocketDataForComments(process.getComments())); if (!process.getTemplates().isEmpty()) { docketdata.setTemplateProperties(getDocketDataForProperties(process.getTemplates())); @@ -2064,6 +2066,17 @@ private static ArrayList getDocketDataForPropert return propertiesForDocket; } + private static List getDocketDataForComments(List comments) { + List commentsForDocket = new ArrayList<>(); + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + for (Comment comment : comments) { + String commentString = dateFormat.format(comment.getCreationDate()) + " " + + comment.getAuthor().getFullName() + ": " + comment.getMessage(); + commentsForDocket.add(commentString); + } + return commentsForDocket; + } + private List> getMetadataForIndex(Process process) { return getMetadataForIndex(process, false); } diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/ProjectService.java b/Kitodo/src/main/java/org/kitodo/production/services/data/ProjectService.java index b64135c8fbe..207c3210bb8 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/ProjectService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/ProjectService.java @@ -152,8 +152,9 @@ public List getAllNotIndexed() { @Override public List getAllForSelectedClient() { - return dao.getByQuery("SELECT p FROM Project AS p INNER JOIN p.client AS c WITH c.id = :clientId", - Collections.singletonMap("clientId", ServiceManager.getUserService().getSessionClientId())); + return dao.getByQuery( + "SELECT p FROM Project AS p INNER JOIN p.client AS c WITH c.id = :clientId ORDER BY title", + Collections.singletonMap("clientId", ServiceManager.getUserService().getSessionClientId())); } @Override diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/TemplateService.java b/Kitodo/src/main/java/org/kitodo/production/services/data/TemplateService.java index b196f66d095..cdb2be13383 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/TemplateService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/TemplateService.java @@ -17,11 +17,13 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -41,6 +43,7 @@ import org.kitodo.data.elasticsearch.search.Searcher; import org.kitodo.data.exceptions.DataException; import org.kitodo.exceptions.ProcessGenerationException; +import org.kitodo.production.dto.ProjectDTO; import org.kitodo.production.dto.TaskDTO; import org.kitodo.production.dto.TemplateDTO; import org.kitodo.production.dto.WorkflowDTO; @@ -204,7 +207,8 @@ public TemplateDTO convertJSONObjectToDTO(Map jsonObject, boolea private void convertRelatedJSONObjects(Map jsonObject, TemplateDTO templateDTO) throws DataException { templateDTO.setProjects(convertRelatedJSONObjectToDTO(jsonObject, TemplateTypeField.PROJECTS.getKey(), - ServiceManager.getProjectService())); + ServiceManager.getProjectService()).stream().sorted(Comparator.comparing(ProjectDTO::getTitle)) + .collect(Collectors.toList())); } /** diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/UserService.java b/Kitodo/src/main/java/org/kitodo/production/services/data/UserService.java index a64211ee634..77d4bdc092c 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/UserService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/UserService.java @@ -164,8 +164,7 @@ public List loadData(int first, int pageSize, String sortField, SortOrder * if no unique user can be found */ public User getByLdapLoginOrLogin(String login) { - List users = getByLoginQuery(login, "from User where ldapLogin = :login"); - users.addAll(getByLoginQuery(login, "from User where login = :login")); + List users = getByLoginQuery(login, "from User where ldapLogin = :login or login = :login"); return uniqueResult(users, login); } @@ -175,6 +174,8 @@ public User getByLdapLoginOrLogin(String login) { * @param login * The login. * @return The user. + * @throws UsernameNotFoundException + * if no unique user can be found */ public User getByLogin(String login) { List users = getByLoginQuery(login, "from User where login = :login"); diff --git a/Kitodo/src/main/java/org/kitodo/production/services/file/FileService.java b/Kitodo/src/main/java/org/kitodo/production/services/file/FileService.java index b258f24d26f..a5fb16915b0 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/file/FileService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/file/FileService.java @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -33,6 +34,7 @@ import java.util.TreeMap; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.apache.commons.collections4.BidiMap; import org.apache.commons.collections4.bidimap.DualHashBidiMap; @@ -64,12 +66,16 @@ import org.kitodo.production.helper.metadata.ImageHelper; import org.kitodo.production.helper.metadata.legacytypeimplementations.LegacyMetsModsDigitalDocumentHelper; import org.kitodo.production.helper.metadata.pagination.Paginator; +import org.kitodo.production.helper.tasks.TaskManager; import org.kitodo.production.metadata.MetadataEditor; +import org.kitodo.production.metadata.MetadataLock; import org.kitodo.production.model.Subfolder; import org.kitodo.production.services.ServiceManager; import org.kitodo.production.services.command.CommandService; import org.kitodo.production.services.data.RulesetService; +import org.kitodo.production.thread.RenameMediaThread; import org.kitodo.serviceloader.KitodoServiceLoader; +import org.kitodo.utils.MediaUtil; public class FileService { @@ -1113,7 +1119,7 @@ public boolean searchForMedia(Process process, Workpiece workpiece) } addNewURIsToExistingPhysicalDivisions(currentMedia, - workpiece.getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted(), canonicals); + workpiece.getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack(), canonicals); Map> mediaToAdd = new TreeMap<>(currentMedia); List mediaToRemove = new LinkedList<>(canonicals); @@ -1162,7 +1168,7 @@ private List getCanonicalFileNamePartsAndSanitizeAbsoluteURIs(Workpiece if (!baseUriString.endsWith("/")) { baseUriString = baseUriString.concat("/"); } - for (PhysicalDivision physicalDivision : workpiece.getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted()) { + for (PhysicalDivision physicalDivision : workpiece.getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack()) { String unitCanonical = ""; for (Entry entry : physicalDivision.getMediaFiles().entrySet()) { Subfolder subfolder = subfolders.get(entry.getKey().getUse()); @@ -1211,7 +1217,7 @@ private void addNewURIsToExistingPhysicalDivisions(Map mediaToRemove, Workpiece workpiece, Collection subfolders) { - List pages = workpiece.getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted(); + List pages = workpiece.getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack(); for (String removal : mediaToRemove) { if (StringUtils.isNotBlank(removal)) { for (PhysicalDivision page : pages) { @@ -1233,7 +1239,7 @@ private void removeMissingMediaFromWorkpiece(List mediaToRemove, Workpie } if (!mediaToRemove.isEmpty()) { int i = 1; - for (PhysicalDivision division : workpiece.getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted()) { + for (PhysicalDivision division : workpiece.getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack()) { division.setOrder(i); i++; } @@ -1311,6 +1317,13 @@ private PhysicalDivision createPhysicalDivision(Map data) { for (Entry entry : data.entrySet()) { Folder folder = entry.getKey().getFolder(); MediaVariant mediaVariant = createMediaVariant(folder); + + // overwrite physical division type if mime type is audio or video + if (!PhysicalDivision.TYPE_TRACK.equals(physicalDivision.getType()) && MediaUtil.isAudioOrVideo( + mediaVariant.getMimeType())) { + physicalDivision.setType(PhysicalDivision.TYPE_TRACK); + } + physicalDivision.getMediaFiles().put(mediaVariant, entry.getValue()); } return physicalDivision; @@ -1331,7 +1344,8 @@ private MediaVariant createMediaVariant(Folder folder) { */ public void renumberPhysicalDivisions(Workpiece workpiece, boolean sortByOrder) { int order = 1; - for (PhysicalDivision physicalDivision : sortByOrder ? workpiece.getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted() + for (PhysicalDivision physicalDivision : sortByOrder + ? workpiece.getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack() : workpiece.getPhysicalStructure().getAllChildren()) { physicalDivision.setOrder(order++); } @@ -1343,7 +1357,7 @@ public void renumberPhysicalDivisions(Workpiece workpiece, boolean sortByOrder) * intermediate places are marked uncounted. */ private void repaginatePhysicalDivisions(Workpiece workpiece) { - List physicalDivisions = workpiece.getAllPhysicalDivisionChildrenFilteredByTypePageAndSorted(); + List physicalDivisions = workpiece.getAllPhysicalDivisionChildrenSortedFilteredByPageAndTrack(); int first = 0; String value; switch (ConfigCore.getParameter(ParameterCore.METS_EDITOR_DEFAULT_PAGINATION)) { @@ -1515,6 +1529,52 @@ public int renameMediaFiles(Process process, Workpiece workpiece, DualHashBidiMa return numberOfRenamedMedia; } + /** + * Check whether too many processes are selected for media renaming and return corresponding error message. + * @param numberOfProcesses number of processes for media renaming + * @return error message if too many processes are selected; otherwise return empty string + */ + public String tooManyProcessesSelectedForMediaRenaming(int numberOfProcesses) { + int limit = ConfigCore.getIntParameterOrDefaultValue(ParameterCore.MAX_NUMBER_OF_PROCESSES_FOR_MEDIA_RENAMING); + if (0 < limit && limit < numberOfProcesses) { + return Helper.getTranslation("tooManyProcessesSelectedForMediaRenaming", String.valueOf(limit), + String.valueOf(numberOfProcesses)); + } else { + return ""; + } + } + + /** + * Rename media files of given processes. + * @param processes Processes whose media is renamed + */ + public void renameMedia(List processes) { + processes = lockAndSortProcessesForRenaming(processes); + TaskManager.addTask(new RenameMediaThread(processes)); + } + + private List lockAndSortProcessesForRenaming(List processes) { + processes.sort(Comparator.comparing(Process::getId)); + List lockedProcesses = new LinkedList<>(); + for (Process process : processes) { + int processId = process.getId(); + if (MetadataLock.isLocked(processId)) { + lockedProcesses.add(processId); + if (ConfigCore.getBooleanParameterOrDefaultValue(ParameterCore.ANONYMIZE)) { + logger.error("Unable to lock process " + processId + " for media renaming because it is currently " + + "being worked on by another user"); + } else { + User currentUser = MetadataLock.getLockUser(processId); + logger.error("Unable to lock process " + processId + " for media renaming because it is currently " + + "being worked on by another user (" + currentUser.getFullName() + ")"); + } + } else { + MetadataLock.setLocked(processId, ServiceManager.getUserService().getCurrentUser()); + } + } + return processes.stream().filter(p -> !lockedProcesses.contains(p.getId())).collect(Collectors.toList()); + } + /** * Revert renaming of media files when the user leaves the metadata editor without saving. This method uses a * provided map object to rename media files identified by the map entries values to the corresponding map entries @@ -1525,6 +1585,7 @@ public int renameMediaFiles(Process process, Workpiece workpiece, DualHashBidiMa */ public void revertRenaming(BidiMap filenameMappings, Workpiece workpiece) { // revert media variant URIs for all media files in workpiece to previous, original values + logger.info("Reverting to original media filenames of process " + workpiece.getId()); for (PhysicalDivision physicalDivision : workpiece .getAllPhysicalDivisionChildrenFilteredByTypes(PhysicalDivision.TYPES)) { for (Entry mediaVariantURIEntry : physicalDivision.getMediaFiles().entrySet()) { @@ -1536,8 +1597,14 @@ public void revertRenaming(BidiMap filenameMappings, Workpiece workpie try { List tempUris = new LinkedList<>(); for (Entry mapping : filenameMappings.entrySet()) { - tempUris.add(fileManagementModule.rename(mapping.getKey(), mapping.getValue().toString() - + TEMP_EXTENSION)); + if (mapping.getKey().toString().endsWith(TEMP_EXTENSION)) { + // if current URI has '.tmp' extension, directly revert to original name (without '.tmp' extension) + tempUris.add(fileManagementModule.rename(mapping.getKey(), mapping.getValue().toString())); + } else { + // rename to new filename with '.tmp' extension otherwise + tempUris.add(fileManagementModule.rename(mapping.getKey(), mapping.getValue().toString() + + TEMP_EXTENSION)); + } } for (URI tempUri : tempUris) { fileManagementModule.rename(tempUri, StringUtils.removeEnd(tempUri.toString(), TEMP_EXTENSION)); diff --git a/Kitodo/src/main/java/org/kitodo/production/services/ocr/OcrdWorkflowService.java b/Kitodo/src/main/java/org/kitodo/production/services/ocr/OcrdWorkflowService.java new file mode 100644 index 00000000000..ab8d4f41e18 --- /dev/null +++ b/Kitodo/src/main/java/org/kitodo/production/services/ocr/OcrdWorkflowService.java @@ -0,0 +1,102 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * This file is part of the Kitodo project. + * + * It is licensed under GNU General Public License version 3 or later. + * + * For the full copyright and license information, please read the + * GPL3-License.txt file that was distributed with this source code. + */ + +package org.kitodo.production.services.ocr; + +import java.io.IOException; +import java.nio.file.FileVisitOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.kitodo.config.ConfigCore; +import org.kitodo.config.enums.ParameterCore; + +public class OcrdWorkflowService { + + private static final Logger logger = LogManager.getLogger(OcrdWorkflowService.class); + private static volatile OcrdWorkflowService instance = null; + + /** + * Return singleton variable of type OcrdWorkflowService. + * + * @return unique instance of OcrdWorkflowService + */ + public static OcrdWorkflowService getInstance() { + OcrdWorkflowService localReference = instance; + if (Objects.isNull(localReference)) { + synchronized (OcrdWorkflowService.class) { + localReference = instance; + if (Objects.isNull(localReference)) { + localReference = new OcrdWorkflowService(); + instance = localReference; + } + } + } + return localReference; + } + + /** + * Get list of OCR-D workflows from directory. + *

+ * The files are relative paths to the OCR-D workflow directory. + *

+ * + * @return list of OCR-D workflows + */ + public List> getOcrdWorkflows() { + String ocrdWorkflowsDirectoryConfig = ConfigCore.getParameterOrDefaultValue(ParameterCore.OCRD_WORKFLOWS_DIR); + if (StringUtils.isNotEmpty(ocrdWorkflowsDirectoryConfig)) { + Path ocrdWorkflowsDirectory = Path.of(ocrdWorkflowsDirectoryConfig); + if (Files.isDirectory(ocrdWorkflowsDirectory)) { + try (Stream ocrProfilePaths = Files.walk(ocrdWorkflowsDirectory, FileVisitOption.FOLLOW_LINKS)) { + return ocrProfilePaths.filter(Files::isRegularFile).map(Path::toAbsolutePath).sorted() + .map(path -> getImmutablePairFromPath(path, ocrdWorkflowsDirectory)) + .collect(Collectors.toList()); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + } + } + return new ArrayList<>(); + } + + private static Pair getImmutablePairFromPath(Path filePath, Path ocrdWorkflowsDirectory) { + String path = filePath.toString(); + path = path.replace(ocrdWorkflowsDirectory.toString(), StringUtils.EMPTY); + path = StringUtils.removeStart(path, "/"); + return ImmutablePair.of(path, path); + } + + /** + * Get an OCR-D workflow by identifier. + * + * @param ocrdWorkflowId The OCR-D workflow identifier + * @return The OCR-D workflow + */ + public Pair getOcrdWorkflow(String ocrdWorkflowId) { + if (StringUtils.isNotEmpty(ocrdWorkflowId)) { + return getOcrdWorkflows().stream().filter(pair -> pair.getKey().equals(ocrdWorkflowId)).findFirst() + .orElse(null); + } + return null; + } + +} diff --git a/Kitodo/src/main/java/org/kitodo/production/services/security/SecurityAccessService.java b/Kitodo/src/main/java/org/kitodo/production/services/security/SecurityAccessService.java index 963344c9b9d..97a1967786a 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/security/SecurityAccessService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/security/SecurityAccessService.java @@ -233,6 +233,15 @@ public boolean hasAuthorityToAddOnProjectPage() { return hasAnyAuthorityForClient("addProject, addTemplate, addWorkflow, addDocket, addRuleset"); } + /** + * Check if the current user has the authority to assign an OCR-D workflow to template or process. + * + * @return true if the current user has the authority to assign an OCR-D workflow + */ + public boolean hasAuthorityToAssignOcrdWorkflow() { + return hasAnyAuthorityForClient("assignOcrdWorkflow"); + } + /** * Check if the current user has the authority to delete the batch. * diff --git a/Kitodo/src/main/java/org/kitodo/production/thread/RenameMediaThread.java b/Kitodo/src/main/java/org/kitodo/production/thread/RenameMediaThread.java new file mode 100644 index 00000000000..305e1684ade --- /dev/null +++ b/Kitodo/src/main/java/org/kitodo/production/thread/RenameMediaThread.java @@ -0,0 +1,89 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * This file is part of the Kitodo project. + * + * It is licensed under GNU General Public License version 3 or later. + * + * For the full copyright and license information, please read the + * GPL3-License.txt file that was distributed with this source code. + */ + +package org.kitodo.production.thread; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Objects; + +import org.apache.commons.collections4.bidimap.DualHashBidiMap; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.kitodo.api.dataformat.Workpiece; +import org.kitodo.data.database.beans.Process; +import org.kitodo.production.helper.Helper; +import org.kitodo.production.helper.LocaleHelper; +import org.kitodo.production.helper.tasks.EmptyTask; +import org.kitodo.production.metadata.MetadataLock; +import org.kitodo.production.services.ServiceManager; + +/** + * This class is used to rename media files of multiple processes in a separate thread whose progress can be monitored + * in the task manager. + */ +public class RenameMediaThread extends EmptyTask { + + private static final Logger logger = LogManager.getLogger(RenameMediaThread.class); + private final List processes; + private static final String THREAD_NAME = "renameMediaThread"; + private static final String ERRORS_OCCURRED_KEY = "errorsOccurredCheckLog"; + private static final String PROCESSES = "processes"; + + public RenameMediaThread(List processes) { + super(processes.size() + " " + Helper.getString(LocaleHelper.getCurrentLocale(), PROCESSES)); + this.processes = processes; + } + + @Override + protected void setNameDetail(String detail) { + String localThreadName = Helper.getString(LocaleHelper.getCurrentLocale(), THREAD_NAME); + super.setName(localThreadName + ": " + detail); + } + + /** + * Run method of this thread. Iterates over processes and renames all media files in each process using the regular + * "media renaming" functionality of the FileService. + */ + @Override + public void run() { + for (Process process : processes) { + int processId = process.getId(); + URI metaXmlUri = ServiceManager.getProcessService().getMetadataFileUri(process); + DualHashBidiMap renamingMap = new DualHashBidiMap<>(); + Workpiece workpiece = null; + try { + workpiece = ServiceManager.getMetsService().loadWorkpiece(metaXmlUri); + int numberOfRenamedFiles = ServiceManager.getFileService().renameMediaFiles(process, workpiece, + renamingMap); + try (OutputStream out = ServiceManager.getFileService().write(metaXmlUri)) { + ServiceManager.getMetsService().save(workpiece, out); + logger.info("Renamed " + numberOfRenamedFiles + " media files for process " + process.getId()); + } + } catch (IOException | URISyntaxException e) { + logger.error(e.getMessage()); + String nameDetailMessage = processes.size() + + " " + Helper.getString(LocaleHelper.getCurrentLocale(), PROCESSES) + + " (" + Helper.getString(LocaleHelper.getCurrentLocale(), ERRORS_OCCURRED_KEY) + ")"; + this.setNameDetail(nameDetailMessage); + if (Objects.nonNull(workpiece)) { + ServiceManager.getFileService().revertRenaming(renamingMap.inverseBidiMap(), workpiece); + } + } + MetadataLock.setFree(processId); + setProgress((100 / processes.size()) * (processes.indexOf(process) + 1)); + } + setProgress(100); + } +} diff --git a/Kitodo/src/main/resources/docket.xsl b/Kitodo/src/main/resources/docket.xsl index 43608a11383..f4c3774e45f 100644 --- a/Kitodo/src/main/resources/docket.xsl +++ b/Kitodo/src/main/resources/docket.xsl @@ -333,13 +333,15 @@ - - - - - - - + + + + + + + + + diff --git a/Kitodo/src/main/resources/docket_multipage.xsl b/Kitodo/src/main/resources/docket_multipage.xsl index ff6c22bdc17..f207a4519ad 100644 --- a/Kitodo/src/main/resources/docket_multipage.xsl +++ b/Kitodo/src/main/resources/docket_multipage.xsl @@ -334,13 +334,15 @@ - - - - - - - + + + + + + + + + diff --git a/Kitodo/src/main/resources/kitodo_config.properties b/Kitodo/src/main/resources/kitodo_config.properties index 4f00df84098..e1fb72ff1c8 100644 --- a/Kitodo/src/main/resources/kitodo_config.properties +++ b/Kitodo/src/main/resources/kitodo_config.properties @@ -71,6 +71,9 @@ directory.modules=/usr/local/kitodo/modules/ # Falls Dateien zum Debuggen / Tracen geschrieben werden sollen, hier ein Verzeichnis angeben directory.debug=/usr/local/kitodo/debug/ +# Absolute path to the directory that the OCR-D workflow files will be +# read from. It must be terminated by a directory separator ("/"). +ocrd.workflows.directory=/usr/local/kitodo/ocrd_workflows/ # ----------------------------------- # Directory management @@ -764,3 +767,7 @@ security.secret.ldapManagerPassword= # largest data tables of tasks by processingBegin and process by creation date are considered. # The dates can be defined & separated in the format YYYY, YYYY-MM or YYYY-MM-DD e.g. 2017-05-10,2018-06,2022 # database.subset.dates= + +# This optional parameter can be used to limit the number of processes for which media renaming can be conducted as a +# list function. Values different from positive integers are interpreted as "unlimited". +#maxNumberOfProcessesForMediaRenaming=10000 diff --git a/Kitodo/src/main/resources/kitodo_fileFormats.xml b/Kitodo/src/main/resources/kitodo_fileFormats.xml index bc376cada58..5305a3608a6 100644 --- a/Kitodo/src/main/resources/kitodo_fileFormats.xml +++ b/Kitodo/src/main/resources/kitodo_fileFormats.xml @@ -70,4 +70,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/Kitodo/src/main/resources/messages/errors_de.properties b/Kitodo/src/main/resources/messages/errors_de.properties index 7fd7b25ae37..af932d30975 100644 --- a/Kitodo/src/main/resources/messages/errors_de.properties +++ b/Kitodo/src/main/resources/messages/errors_de.properties @@ -126,6 +126,7 @@ noUserInStep=Der Aufgabe {0} wurden keine Nutzer zugeordnet. # O errorOccurred=Es ist ein Fehler aufgetreten: +errorsOccurredCheckLog=Es sind Fehler aufgetreten. Bitte \u00FCberprf\u00FCfen Sie kitodo.log f\u00FCr zus\u00E4tzliche Details. # P errorParsingFile=Parsingfehler: verschiedene {0} in der Datei (''{1}'' & ''{2}''). @@ -167,6 +168,7 @@ templateAssignedError=Der Workflow kann nicht gel\u00F6scht werden, da diesem Pr templateTitleAlreadyInUse=Der Produktionsvorlagentitel wird bereits verwendet. templateTitleEmpty=Kein Produktionsvorlagentitel angegeben. tooManyBatchesSelected=Es sind mehrere Batches ausgew\u00E4hlt. W\u00E4hlen Sie genau einen Batch aus, um diese Funktion zu nutzen. +tooManyProcessesSelectedForMediaRenaming=Es wurden {0} Vorg\u00E4nge f\u00FCr die Medienumbenennung ausgew\u00E4lt. Die maximal erlaubte Anzahl von Vorg\u00E4ngen f\u00FCr die Medienumbenennung liegt bei {1}. Bitte reduzieren Sie die Menge der ausgew\u00E4lten Vorg\u00E4nge und wiederholen Sie die Aktion. # U errorUploading=Fehler beim Hochladen von ''{0}''. diff --git a/Kitodo/src/main/resources/messages/errors_en.properties b/Kitodo/src/main/resources/messages/errors_en.properties index 39434cbf6c7..22cb8d393b4 100644 --- a/Kitodo/src/main/resources/messages/errors_en.properties +++ b/Kitodo/src/main/resources/messages/errors_en.properties @@ -126,6 +126,7 @@ noUserInStep=No user assigned to step {0}. # O errorOccurred=An error has occurred: +errorsOccurredCheckLog=Errors occurred. Please check kitodo.log for Details. # P errorParsingFile=Error parsing: various {0} in the file (''{1}'' & ''{2}''). @@ -166,6 +167,7 @@ templateAssignedError=The workflow could not be deleted because there are alread templateTitleAlreadyInUse=The template title is already in use. templateTitleEmpty=No template title stated. tooManyBatchesSelected=Multiple batches are selected. To use this function, please select one batch only. +tooManyProcessesSelectedForMediaRenaming={0} have been selected for media renaming. The maximum number of processes for media renaming is {1}. Please reduce the number of selected processes and try again. # U errorUploading=Error uploading ''{0}''. diff --git a/Kitodo/src/main/resources/messages/messages_de.properties b/Kitodo/src/main/resources/messages/messages_de.properties index 93a139d10b9..da55bd6ded2 100644 --- a/Kitodo/src/main/resources/messages/messages_de.properties +++ b/Kitodo/src/main/resources/messages/messages_de.properties @@ -9,6 +9,12 @@ # GPL3-License.txt file that was distributed with this source code. # AND=Enth\u00E4lt +audioWaveformToolsCenteredCursor=zentrierter Cursor +audioWaveformToolsJumpForwardFiveSeconds=5 Sekunden vorspringen +audioWaveformToolsJumpForwardOneSecond=1 Sekunde vorspringen +audioWaveformToolsJumpBackFiveSeconds=5 Sekunden zur\u00FCckspringen +audioWaveformToolsJumpBackOneSecond=1 Sekunde zur\u00FCckspringen +audioWaveformToolsZoom=Zoom authorities=Berechtigungen authority=Berechtigung DMSExportByThread=Der Vorgang wird vom Taskmanager ins DMS exportiert\: @@ -52,6 +58,7 @@ ARCHIVED=Archiviert assigned=Zugewiesen assignedTo=In Bearbeitung durch assignMedia=und ausgew\u00E4hlte Medien zuweisen +assignOcrdWorkflow=OCR-D Workflow zuweisen assignToNextElement=Mit n\u00E4chstem Element verkn\u00FCpfen ats=ATS actions=Aktionen @@ -189,6 +196,8 @@ columnConfigurationSaved=Spaltenkonfiguration gespeichert columnCount=Spaltenz\u00E4hlung comment=Kommentieren comments=Kommentare +commentDelete=Kommentar l\u00F6schen +commentEdit=Kommentar bearbeiten commentWrite=Kommentar schreiben configureColumns=Spalten konfigurieren configurationChanged=Einstellungen ge\u00E4ndert @@ -222,7 +231,6 @@ copyRolesToClient=Rollen zum Mandanten hinzuf\u00FCgen copyTemplates=Projekt inkl. Templates kopieren correction=Korrektur correctionFor=Korrektur f\u00FCr Schritt -correctionK=K correctionMessage=Korrekturmeldung correctionMessageSend=Korrekturmeldung senden correctionMessageSendAllTasks=Korrekturmeldung f\u00FCr alle Schritte senden @@ -318,7 +326,6 @@ dataEditor.removeElement.noConsecutivePagesSelected=Strukturelemente k\u00F6nnen dataEditor.selectMetadataTask=Aufgabe w\u00E4hlen dataEditor.layoutSavedSuccessfullyTitle=Aktuelle Spaltenaufteilung erfolgreich gespeichert dataEditor.layoutSavedSuccessfullyText=Ihre aktuellen Metadaten-Editor-Einstellungen wurden erfolgreich gespeichert! Sie werden f\u00FCr alle zuk\u00FCnftigen Aufgaben dieses Typs wiederverwendet. -dataEditor.renameMedia=Medien umbenennen dataEditor.renamingMediaComplete=Das Umbenennen der Medien ist abgeschlossen dataEditor.renamingMediaError=Beim Umbenennen der Medien ist ein Fehler aufgetreten dataEditor.renamingMediaText={0} Mediendateien in {1} Ordnern wurden umbenannt. Bitte Speichern Sie den Vorgang. Andernfalls werden die Dateiumbennungen beim Schlie\u00DFen des Metadateneditors verworfen. @@ -463,6 +470,7 @@ folderUse.audioPreview=Als Vorschau f\u00fcr Audios verwenden folderUse.audioPreview.disabled=Vorschau f\u00fcr Audios deaktiviert folderUse.audioMediaView=F\u00FCr die Medienansicht f\u00fcr Audios verwenden folderUse.audioMediaView.disabled=Medienansicht f\u00fcr Audios deaktiviert +folderUse.audioMediaViewWaveform=Wellenform in der Medienansicht f\u00fcr Audios anzeigen folderUse.generatorSource=Als Quelle zum Generieren von Inhalten verwenden folderUse.generatorSource.disabled=Generieren von Inhalten deaktiviert folderUse.mediaView=F\u00FCr die Medienansicht verwenden @@ -837,6 +845,7 @@ numberVolumes=Anzahl der B\u00E4nde numberOfMetadata=Anzahl Metadaten objectType=Objekttyp ocr=OCR +ocrdWorkflow=OCR-D Workflow ok=Ok oldPassword=Altes Passwort onlySelectedPages=Nur die markierten Seiten @@ -888,6 +897,7 @@ position=Position ppn=PPN priority=Priorit\u00E4t process=Vorgang +processConfig.fromTemplate=aus Produktionsvorlage processData=Vorgangsdaten processDetails=Vorgangsdetails processList=Vorgangsliste @@ -953,6 +963,8 @@ removePhysicalDivision=Physisches Strukturelement entfernen removeRole=Rolle entfernen removeUserFromGroup=Benutzer aus der Benutzergruppe l\u00F6schen renameBatch=Batch umbenennen +renameMediaFiles=Medien umbenennen +renamingMediaFilesOfSelectedProcessesStarted=Das Umbenennen der Medien f\u00FCr die ausgew\u00E4hlten Vorg\u00E4nge wurde erfolgreich gestartet. requiredField=Mit * gekennzeichnete Felder sind Pflichtfelder reset=Zur\u00FCcksetzen resultPDF=Ergebnis-PDF @@ -1273,6 +1285,8 @@ assignTask=Aufgabe zuweisen overrideTask=Aufgabe \u00FCbernehmen superviseTask=Aufgabe beobachten renameMedia=Medien umbenennen +renameMediaThread=Medien umbenennen +renameMediaForProcessesConfirmMessage=Die Mediendateien von {0} Vorg\u00E4ngen werden gem\u00E4ss ihrer Reihenfolge in den jeweiligen Vorg\u00E4ngen umbenannt. Diese Aktion kann nicht r\u00FCckg\u00E4ngig gemacht werden. M\u00F6chten Sie fortfahren? resetWorkflow=Workflow zur\u00FCcksetzen runKitodoScript=KitodoScript ausf\u00FChren diff --git a/Kitodo/src/main/resources/messages/messages_en.properties b/Kitodo/src/main/resources/messages/messages_en.properties index 6b9172fa659..e7db7ed8e38 100644 --- a/Kitodo/src/main/resources/messages/messages_en.properties +++ b/Kitodo/src/main/resources/messages/messages_en.properties @@ -9,6 +9,12 @@ # GPL3-License.txt file that was distributed with this source code. # AND=Contains +audioWaveformToolsCenteredCursor=Centered cursor +audioWaveformToolsJumpForwardFiveSeconds=Jump forward 5 seconds +audioWaveformToolsJumpForwardOneSecond=Jump forward 1 second +audioWaveformToolsJumpBackFiveSeconds=Jump back 5 seconds +audioWaveformToolsJumpBackOneSecond=Jump back 1 second +audioWaveformToolsZoom=Zoom authorities=Authorities authority=Authority DMSExportByThread=The process is being exported into the DMS by the task manager\: @@ -52,6 +58,7 @@ ARCHIVED=Archived assigned=Assigned assignedTo=In edition by assignMedia=and assign selected media +assignOcrdWorkflow=Assign OCR-D workflow assignToNextElement=Assign to next element ats=ATS actions=Actions @@ -189,6 +196,8 @@ columnConfigurationSaved=Column configuration saved columnCount=Column counting comment=Add comment comments=Comments +commentDelete=Delete comment +commentEdit=Edit comment commentWrite=Write comment configureColumns=Configure columns configurationChanged=Configuration changed @@ -222,7 +231,6 @@ copyRolesToClient=Copy roles to client copyTemplates=Copy Project including Templates correction=Correction correctionFor=Correction for step -correctionK=C correctionMessage=Correction message correctionMessageSend=Send correction message correctionMessageSendAllTasks=send correction message to all tasks @@ -318,7 +326,6 @@ dataEditor.removeElement.noConsecutivePagesSelected=Select consecutive pages to dataEditor.selectMetadataTask=Select task dataEditor.layoutSavedSuccessfullyTitle=Current layout successfully saved dataEditor.layoutSavedSuccessfullyText=Your current editor settings have been saved successfully and will be applied to all future tasks of the same type. -dataEditor.renameMedia=Rename media dataEditor.renamingMediaComplete=Finished renaming media dataEditor.renamingMediaError=An error occurred while renaming media files dataEditor.renamingMediaText={0} media files in {1} folders have been renamed. Please click the 'Save' button to persist changes to the filenames. Otherwise the renaming will be reverted upon closing the editor. @@ -464,6 +471,7 @@ folderUse.audioPreview=Use as preview for audios folderUse.audioPreview.disabled=Preview for audios disabled folderUse.audioMediaView=Use for the media view for audios folderUse.audioMediaView.disabled=Media view for audios disabled +folderUse.audioMediaViewWaveform=Show waveform in media view for audios folderUse.generatorSource=Use as source to generate contents folderUse.generatorSource.disabled=Content generation disabled folderUse.mediaView=Use for the media view @@ -838,6 +846,7 @@ numberVolumes=Number of volumes numberOfMetadata=Number of metadata objectType=Object type ocr=OCR +ocrdWorkflow=OCR-D workflow ok=Ok oldPassword=Old password onlySelectedPages=Only the selected pages @@ -889,6 +898,7 @@ position=Position ppn=PPN priority=Priority process=Process +processConfig.fromTemplate=From process template processData=Process data processDetails=Process details processList=Process list @@ -954,6 +964,8 @@ removePhysicalDivision=Remove physical structure element removeRole=Remove role removeUserFromGroup=Delete user from group renameBatch=Rename batch +renameMediaFiles=Rename media +renamingMediaFilesOfSelectedProcessesStarted=Renaming media files of selected processes started successfully. requiredField=Fields marked with * are required reset=Reset resultPDF=Result PDF @@ -1275,6 +1287,8 @@ overrideTask=Take on task superviseTask=Watch task resetWorkflow=Reset workflow renameMedia=Rename media +renameMediaThread=Rename media +renameMediaForProcessesConfirmMessage=The media files of {0} processes will be renamed according to their order in the individual processes. This change cannot be reverted. Do you want to continue? runKitodoScript=Execute KitodoScript viewAllAuthorities=View all authorities diff --git a/Kitodo/src/main/resources/messages/messages_es.properties b/Kitodo/src/main/resources/messages/messages_es.properties index d4e529b04b3..930f88eae15 100644 --- a/Kitodo/src/main/resources/messages/messages_es.properties +++ b/Kitodo/src/main/resources/messages/messages_es.properties @@ -9,6 +9,12 @@ # GPL3-License.txt file that was distributed with this source code. # AND=Contiene +audioWaveformToolsCenteredCursor=Centrar cursor +audioWaveformToolsJumpForwardFiveSeconds=Adelantar 5 segundos +audioWaveformToolsJumpForwardOneSecond=Adelantar 1 segundo +audioWaveformToolsJumpBackFiveSeconds=Retroceder 5 segundos +audioWaveformToolsJumpBackOneSecond=Retroceder 1 segundo +audioWaveformToolsZoom=Zoom authorities=Autorizaciones authority=Autorización DMSExportByThread=La tarea se exporta desde el administrador de tareas al DMS\: @@ -222,7 +228,6 @@ copyRolesToClient=Añadir funciones al cliente copyTemplates=Copiar el proyecto incluyendo las plantillas correction=Corrección correctionFor=Corrección del paso -correctionK=C correctionMessage=Mensaje de corrección correctionMessageSend=Enviar mensaje de corrección correctionMessageSendAllTasks=Enviar mensaje de corrección para todos los pasos @@ -318,7 +323,6 @@ dataEditor.removeElement.noConsecutivePagesSelected=Los elementos estructurales dataEditor.selectMetadataTask=Seleccionar la tarea dataEditor.layoutSavedSuccessfullyTitle=La plantilla actual se guardó correctamente dataEditor.layoutSavedSuccessfullyText=Su configuración actual del editor ha sido guardada con éxito y será aplicadad a todas las tareas futuras del mismo tipo. -dataEditor.renameMedia=Cambiar el nombre de los archivos de medios dataEditor.renamingMediaComplete=El cambio de nombre de los medios ha finalizado dataEditor.renamingMediaError=Se produjo un error al cambiar el nombre de los archivos multimedia dataEditor.renamingMediaText=Se ha cambiado el nombre de {0} archivos de medios en {1} carpeta. Por favor, haga clic en el botón 'Guardar' para mantener los cambios en los nombres de archivo. De lo contrario, el cambio de nombre se revertirá al cerrar el editor. @@ -466,6 +470,7 @@ folderUse.generatorSource=Utilizar como fuente para generar contenidos folderUse.generatorSource.disabled=Generación de contenido desactivada folderUse.mediaView=Usar para la vista multimedia folderUse.mediaView.disabled=Vista de medios desactivada +folderUse.audioMediaViewWaveform=Mostrar la forma de onda en la vista de medios para audio folderUse.preview=Utilizar como vista previa folderUse.preview.disabled=Vista previa deshabilitada folderUse.videoPreview=Usar como vista previa para videos @@ -952,6 +957,7 @@ removePhysicalDivision=Eliminar la posición de la estructura física removeRole=Eliminar el rol removeUserFromGroup=Eliminar un usuario del grupo de usuarios renameBatch=Cambiar el nombre del lote +renameMediaFiles=Cambiar el nombre de los archivos de medios requiredField=Los campos marcados con * son obligatorios reset=Restablecer resultPDF=Resultado PDF @@ -1083,7 +1089,7 @@ taskManagerIsRunning=No iniciar las tareas automáticamente taskManagerIsStopped=Inicie las tareas automáticamente taskManager=Gestor de tareas taskSaving=La tarea está guardada... -taskScriptThread=Tarea de guión +taskScriptThread=Tarea del script taskStatus=Estado de la tarea tasks=Tareas technicalData=Datos técnicos @@ -1273,6 +1279,7 @@ overrideTask=Asumir la tarea superviseTask=Observar la tarea resetWorkflow=Restablecer el flujo de trabajo renameMedia=Cambiar el nombre de los archivos multimedia +renameMediaThread=Cambiar el nombre de los archivos multimedia runKitodoScript=Ejecutar KitodoScript viewAllAuthorities=Mostrar todos los permisos diff --git a/Kitodo/src/main/resources/xslt/dfg-viewer.xsl b/Kitodo/src/main/resources/xslt/dfg-viewer.xsl index fe27b34bdb1..a38b30f33cc 100644 --- a/Kitodo/src/main/resources/xslt/dfg-viewer.xsl +++ b/Kitodo/src/main/resources/xslt/dfg-viewer.xsl @@ -11,13 +11,135 @@ * GPL3-License.txt file that was distributed with this source code. * --> - + + - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + physSequence + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css b/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css index d9a9c620bb8..ccecde030c4 100644 --- a/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css +++ b/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css @@ -268,7 +268,9 @@ body.first-visit #portal-header { color: inherit; } -#header-nav li:hover a { +#dashboard-menu:hover, +#user-menu:hover { + background: transparent; color: var(--carbon-blue); } @@ -1802,15 +1804,8 @@ Mass import color: var(--medium-gray); } -.correction-marker { - color: var(--focused-orange); - font-size: var(--input-height); - font-weight: bold; -} - .correction { color: var(--focused-orange); - font-weight: bold; } .corrected { @@ -2299,6 +2294,10 @@ form#metadata div label, display: none; } +.comment-correction { + margin-right: var(--group-margin); +} + .comment-author { font-weight: bold; margin-right: .2em; @@ -2323,19 +2322,19 @@ form#metadata div label, text-align: center; } -#newCommentDialog .ui-dialog-content { +#newCommentDialog .ui-dialog-content, #editCommentDialog .ui-dialog-content { padding: var(--default-double-size); } -#newCommentDialog .ui-dialog-titlebar { +#newCommentDialog .ui-dialog-titlebar, #editCommentDialog .ui-dialog-titlebar { display: none; } -#newCommentForm h3 { +#newCommentForm h3, #editCommentForm h3 { padding: 0; } -#newCommentForm\:newCommentTextArea { +#newCommentForm\:newCommentTextArea, #editCommentForm\:editCommentTextArea { width: 100%; margin-bottom: 32px; height: calc(var(--default-double-size) * 3); @@ -3085,15 +3084,35 @@ Column content #imagePreviewForm\:mediaDetail { display: flex; - width: 100%; + width: calc(100% - 100px); align-items: center; justify-content: center; + flex-direction: column; + gap: 24px; } #imagePreviewForm\:mediaDetail .mediaPreviewItem { max-height: 100%; } +#imagePreviewForm\:mediaDetail #audioWaveformTools{ + display: flex; + gap: 30px; + justify-content: center; + align-items: center; + padding: 14px 16px; + background: var(--trans-blue); + border-radius: 3px; +} + +#imagePreviewForm\:mediaDetail #audioWaveformTools > div > button:first-child { + margin-left: 0; +} + +#imagePreviewForm\:mediaDetail #audioWaveformTools > div > button { + margin-left: 10px; +} + #imagePreviewForm .mediaListIconItem { height: 100%; width: 100%; @@ -3952,7 +3971,7 @@ footer { width: 1em; } -.ui-tooltip.comments .icon .correction-marker { +.ui-tooltip.comments .icon .correction { font-size: inherit; } @@ -3968,6 +3987,16 @@ footer { max-width: 300px; } +.loader { + width: 30px; + height: 30px; + color: white; + display: flex; + justify-content: center; + align-items: center; + padding: 10px; +} + /*---------------------------------------------------------------------- min 700px ----------------------------------------------------------------------*/ diff --git a/Kitodo/src/main/webapp/WEB-INF/resources/css/pattern-library.css b/Kitodo/src/main/webapp/WEB-INF/resources/css/pattern-library.css index 7f71a6b9f0f..6a2b2796cfd 100644 --- a/Kitodo/src/main/webapp/WEB-INF/resources/css/pattern-library.css +++ b/Kitodo/src/main/webapp/WEB-INF/resources/css/pattern-library.css @@ -1461,10 +1461,11 @@ Popup dialogs } #loadingScreen { + animation: overlay-delayed .3s .5s forwards; position: fixed; width: 100vw; height: 100vh; - background: rgba(255,255,255,.5); + background: rgba(255,255,255,0); top: 0; left: 0; z-index: 3; @@ -1472,6 +1473,15 @@ Popup dialogs text-align: center; } +@keyframes overlay-delayed { + from { + background: rgba(255,255,255,0); + } + to { + background: rgba(255,255,255,.5); + } +} + #loadingScreen i.fa-spinner { color: var(--blue); } diff --git a/Kitodo/src/main/webapp/WEB-INF/resources/js/libs/wavesurfer/LICENSE b/Kitodo/src/main/webapp/WEB-INF/resources/js/libs/wavesurfer/LICENSE new file mode 100644 index 00000000000..88998ae68f3 --- /dev/null +++ b/Kitodo/src/main/webapp/WEB-INF/resources/js/libs/wavesurfer/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2012-2023, katspaugh and contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Kitodo/src/main/webapp/WEB-INF/resources/js/libs/wavesurfer/wavesurfer.esm.js b/Kitodo/src/main/webapp/WEB-INF/resources/js/libs/wavesurfer/wavesurfer.esm.js new file mode 100644 index 00000000000..013504e3ff4 --- /dev/null +++ b/Kitodo/src/main/webapp/WEB-INF/resources/js/libs/wavesurfer/wavesurfer.esm.js @@ -0,0 +1,2 @@ +// wavesurfer 7.3.2 https://wavesurfer-js.org +function t(t,e,i,s){return new(i||(i=Promise))((function(n,r){function o(t){try{h(s.next(t))}catch(t){r(t)}}function a(t){try{h(s.throw(t))}catch(t){r(t)}}function h(t){var e;t.done?n(t.value):(e=t.value,e instanceof i?e:new i((function(t){t(e)}))).then(o,a)}h((s=s.apply(t,e||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;const e={decode:function(e,i){return t(this,void 0,void 0,(function*(){const t=new AudioContext({sampleRate:i});return t.decodeAudioData(e).finally((()=>t.close()))}))},createBuffer:function(t,e){return"number"==typeof t[0]&&(t=[t]),function(t){const e=t[0];if(e.some((t=>t>1||t<-1))){const i=e.length;let s=0;for(let t=0;ts&&(s=i)}for(const e of t)for(let t=0;tnull==t?void 0:t[e],copyFromChannel:AudioBuffer.prototype.copyFromChannel,copyToChannel:AudioBuffer.prototype.copyToChannel}}};const i={fetchBlob:function(e,i,s){var n,r;return t(this,void 0,void 0,(function*(){const o=yield fetch(e,s);{const e=null===(n=o.clone().body)||void 0===n?void 0:n.getReader(),s=Number(null===(r=o.headers)||void 0===r?void 0:r.get("Content-Length"));let a=0;const h=(n,r)=>t(this,void 0,void 0,(function*(){if(n)return;a+=(null==r?void 0:r.length)||0;const t=Math.round(a/s*100);return i(t),null==e?void 0:e.read().then((({done:t,value:e})=>h(t,e)))}));null==e||e.read().then((({done:t,value:e})=>h(t,e)))}return o.blob()}))}};class s{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const i=this.on(t,e),s=this.on(t,(()=>{i(),s()}));return i}un(t,e){this.listeners[t]&&(e?this.listeners[t].delete(e):delete this.listeners[t])}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}}class n extends s{constructor(t){super(),t.media?this.media=t.media:this.media=document.createElement("audio"),t.mediaControls&&(this.media.controls=!0),t.autoplay&&(this.media.autoplay=!0),null!=t.playbackRate&&this.onceMediaEvent("canplay",(()=>{null!=t.playbackRate&&(this.media.playbackRate=t.playbackRate)}))}onMediaEvent(t,e,i){return this.media.addEventListener(t,e,i),()=>this.media.removeEventListener(t,e)}onceMediaEvent(t,e){return this.onMediaEvent(t,e,{once:!0})}getSrc(){return this.media.currentSrc||this.media.src||""}revokeSrc(){const t=this.getSrc();t.startsWith("blob:")&&URL.revokeObjectURL(t)}setSrc(t,e){if(this.getSrc()===t)return;this.revokeSrc();const i=e instanceof Blob?URL.createObjectURL(e):t;this.media.src=i,this.media.load()}destroy(){this.media.pause(),this.revokeSrc(),this.media.src="",this.media.load()}play(){return this.media.play()}pause(){this.media.pause()}isPlaying(){return this.media.currentTime>0&&!this.media.paused&&!this.media.ended}setTime(t){this.media.currentTime=t}getDuration(){return this.media.duration}getCurrentTime(){return this.media.currentTime}getVolume(){return this.media.volume}setVolume(t){this.media.volume=t}getMuted(){return this.media.muted}setMuted(t){this.media.muted=t}getPlaybackRate(){return this.media.playbackRate}setPlaybackRate(t,e){null!=e&&(this.media.preservesPitch=e),this.media.playbackRate=t}getMediaElement(){return this.media}setSinkId(t){return this.media.setSinkId(t)}}class r extends s{constructor(t,e){let i;if(super(),this.timeouts=[],this.isScrolling=!1,this.audioData=null,this.resizeObserver=null,this.isDragging=!1,this.options=t,"string"==typeof t.container?i=document.querySelector(t.container):t.container instanceof HTMLElement&&(i=t.container),!i)throw new Error("Container not found");this.parent=i;const[s,n]=this.initHtml();i.appendChild(s),this.container=s,this.scrollContainer=n.querySelector(".scroll"),this.wrapper=n.querySelector(".wrapper"),this.canvasWrapper=n.querySelector(".canvases"),this.progressWrapper=n.querySelector(".progress"),this.cursor=n.querySelector(".cursor"),e&&n.appendChild(e),this.initEvents()}initEvents(){const t=t=>{const e=this.wrapper.getBoundingClientRect(),i=t.clientX-e.left,s=t.clientX-e.left;return[i/e.width,s/e.height]};this.wrapper.addEventListener("click",(e=>{const[i,s]=t(e);this.emit("click",i,s)})),this.wrapper.addEventListener("dblclick",(e=>{const[i,s]=t(e);this.emit("dblclick",i,s)})),this.options.dragToSeek&&this.initDrag(),this.scrollContainer.addEventListener("scroll",(()=>{const{scrollLeft:t,scrollWidth:e,clientWidth:i}=this.scrollContainer,s=t/e,n=(t+i)/e;this.emit("scroll",s,n)}));const e=this.createDelay(100);this.resizeObserver=new ResizeObserver((()=>{e((()=>this.reRender()))})),this.resizeObserver.observe(this.scrollContainer)}initDrag(){!function(t,e,i,s,n=5){let r=()=>{};if(!t)return r;const o=o=>{if(2===o.button)return;o.preventDefault(),o.stopPropagation();let a=o.clientX,h=o.clientY,l=!1;const d=s=>{s.preventDefault(),s.stopPropagation();const r=s.clientX,o=s.clientY;if(l||Math.abs(r-a)>=n||Math.abs(o-h)>=n){const{left:s,top:n}=t.getBoundingClientRect();l||(l=!0,null==i||i(a-s,h-n)),e(r-a,o-h,r-s,o-n),a=r,h=o}},c=t=>{l&&(t.preventDefault(),t.stopPropagation())},u=()=>{l&&(null==s||s()),r()};document.addEventListener("pointermove",d),document.addEventListener("pointerup",u),document.addEventListener("pointerleave",u),document.addEventListener("click",c,!0),r=()=>{document.removeEventListener("pointermove",d),document.removeEventListener("pointerup",u),document.removeEventListener("pointerleave",u),setTimeout((()=>{document.removeEventListener("click",c,!0)}),10)}};t.addEventListener("pointerdown",o)}(this.wrapper,((t,e,i)=>{this.emit("drag",Math.max(0,Math.min(1,i/this.wrapper.getBoundingClientRect().width)))}),(()=>this.isDragging=!0),(()=>this.isDragging=!1))}getHeight(){return null==this.options.height?128:isNaN(Number(this.options.height))?"auto"===this.options.height&&this.parent.clientHeight||128:Number(this.options.height)}initHtml(){const t=document.createElement("div"),e=t.attachShadow({mode:"open"});return e.innerHTML=`\n \n\n
\n
\n
\n
\n
\n
\n
\n `,[t,e]}setOptions(t){this.options=t,this.reRender()}getWrapper(){return this.wrapper}getScroll(){return this.scrollContainer.scrollLeft}destroy(){var t;this.container.remove(),null===(t=this.resizeObserver)||void 0===t||t.disconnect()}createDelay(t=10){const e={};return this.timeouts.push(e),i=>{e.timeout&&clearTimeout(e.timeout),e.timeout=setTimeout(i,t)}}convertColorValues(t){if(!Array.isArray(t))return t||"";if(t.length<2)return t[0]||"";const e=document.createElement("canvas"),i=e.getContext("2d").createLinearGradient(0,0,0,e.height),s=1/(t.length-1);return t.forEach(((t,e)=>{const n=e*s;i.addColorStop(n,t)})),i}renderBarWaveform(t,e,i,s){const n=t[0],r=t[1]||t[0],o=n.length,{width:a,height:h}=i.canvas,l=h/2,d=window.devicePixelRatio||1,c=e.barWidth?e.barWidth*d:1,u=e.barGap?e.barGap*d:e.barWidth?c/2:0,p=e.barRadius||0,m=a/(c+u)/o,g=p&&"roundRect"in i?"roundRect":"rect";i.beginPath();let v=0,f=0,b=0;for(let t=0;t<=o;t++){const o=Math.round(t*m);if(o>v){const t=Math.round(f*l*s),n=t+Math.round(b*l*s)||1;let r=l-t;"top"===e.barAlign?r=0:"bottom"===e.barAlign&&(r=h-n),i[g](v*(c+u),r,c,n,p),v=o,f=0,b=0}const a=Math.abs(n[t]||0),d=Math.abs(r[t]||0);a>f&&(f=a),d>b&&(b=d)}i.fill(),i.closePath()}renderLineWaveform(t,e,i,s){const n=e=>{const n=t[e]||t[0],r=n.length,{height:o}=i.canvas,a=o/2,h=i.canvas.width/r;i.moveTo(0,a);let l=0,d=0;for(let t=0;t<=r;t++){const r=Math.round(t*h);if(r>l){const t=a+(Math.round(d*a*s)||1)*(0===e?-1:1);i.lineTo(l,t),l=r,d=0}const o=Math.abs(n[t]||0);o>d&&(d=o)}i.lineTo(l,a)};i.beginPath(),n(0),n(1),i.fill(),i.closePath()}renderWaveform(t,e,i){if(i.fillStyle=this.convertColorValues(e.waveColor),e.renderFunction)return void e.renderFunction(t,i);let s=e.barHeight||1;if(e.normalize){const e=Array.from(t[0]).reduce(((t,e)=>Math.max(t,Math.abs(e))),0);s=e?1/e:1}e.barWidth||e.barGap||e.barAlign?this.renderBarWaveform(t,e,i,s):this.renderLineWaveform(t,e,i,s)}renderSingleCanvas(t,e,i,s,n,r,o,a){const h=window.devicePixelRatio||1,l=document.createElement("canvas"),d=t[0].length;l.width=Math.round(i*(r-n)/d),l.height=s*h,l.style.width=`${Math.floor(l.width/h)}px`,l.style.height=`${s}px`,l.style.left=`${Math.floor(n*i/h/d)}px`,o.appendChild(l);const c=l.getContext("2d");this.renderWaveform(t.map((t=>t.slice(n,r))),e,c);const u=l.cloneNode();a.appendChild(u);const p=u.getContext("2d");l.width>0&&l.height>0&&p.drawImage(l,0,0),p.globalCompositeOperation="source-in",p.fillStyle=this.convertColorValues(e.progressColor),p.fillRect(0,0,l.width,l.height)}renderChannel(t,e,i){const s=document.createElement("div"),n=this.getHeight();s.style.height=`${n}px`,this.canvasWrapper.style.minHeight=`${n}px`,this.canvasWrapper.appendChild(s);const o=s.cloneNode();this.progressWrapper.appendChild(o);const{scrollLeft:a,scrollWidth:h,clientWidth:l}=this.scrollContainer,d=t[0].length,c=d/h;let u=Math.min(r.MAX_CANVAS_WIDTH,l);if(e.barWidth||e.barGap){const t=e.barWidth||.5,i=t+(e.barGap||t/2);u%i!=0&&(u=Math.floor(u/i)*i)}const p=Math.floor(Math.abs(a)*c),m=Math.floor(p+u*c),g=m-p,v=(r,a)=>{this.renderSingleCanvas(t,e,i,n,Math.max(0,r),Math.min(a,d),s,o)},f=this.createDelay(),b=this.createDelay(),y=(t,e)=>{v(t,e),t>0&&f((()=>{y(t-g,e-g)}))},C=(t,e)=>{v(t,e),e{C(t+g,e+g)}))};y(p,m),mt.timeout&&clearTimeout(t.timeout))),this.timeouts=[],this.canvasWrapper.innerHTML="",this.progressWrapper.innerHTML="",this.wrapper.style.width="";const e=window.devicePixelRatio||1,i=this.scrollContainer.clientWidth,s=Math.ceil(t.duration*(this.options.minPxPerSec||0));this.isScrolling=s>i;const n=this.options.fillParent&&!this.isScrolling,r=(n?i:s)*e;if(this.wrapper.style.width=n?"100%":`${s}px`,this.scrollContainer.style.overflowX=this.isScrolling?"auto":"hidden",this.scrollContainer.classList.toggle("noScrollbar",!!this.options.hideScrollbar),this.cursor.style.backgroundColor=`${this.options.cursorColor||this.options.progressColor}`,this.cursor.style.width=`${this.options.cursorWidth}px`,this.options.splitChannels)for(let e=0;e1&&e.push(t.getChannelData(1)),this.renderChannel(e,this.options,r)}this.audioData=t,this.emit("render")}reRender(){if(!this.audioData)return;const t=this.progressWrapper.clientWidth;this.render(this.audioData);const e=this.progressWrapper.clientWidth;this.scrollContainer.scrollLeft+=e-t}zoom(t){this.options.minPxPerSec=t,this.reRender()}scrollIntoView(t,e=!1){const{clientWidth:i,scrollLeft:s,scrollWidth:n}=this.scrollContainer,r=n*t,o=i/2;if(r>s+(e&&this.options.autoCenter&&!this.isDragging?o:i)||r=t&&r{}}start(){this.unsubscribe=this.on("tick",(()=>{requestAnimationFrame((()=>{this.emit("tick")}))})),this.emit("tick")}stop(){this.unsubscribe()}destroy(){this.unsubscribe()}}const a={waveColor:"#999",progressColor:"#555",cursorWidth:1,minPxPerSec:0,fillParent:!0,interact:!0,dragToSeek:!1,autoScroll:!0,autoCenter:!0,sampleRate:8e3};class h extends n{static create(t){return new h(t)}constructor(t){var e,i;super({media:t.media,mediaControls:t.mediaControls,autoplay:t.autoplay,playbackRate:t.audioRate}),this.plugins=[],this.decodedData=null,this.subscriptions=[],this.options=Object.assign({},a,t),this.timer=new o;const s=t.media?void 0:this.getMediaElement();this.renderer=new r(this.options,s),this.initPlayerEvents(),this.initRendererEvents(),this.initTimerEvents(),this.initPlugins();const n=this.options.url||(null===(e=this.options.media)||void 0===e?void 0:e.currentSrc)||(null===(i=this.options.media)||void 0===i?void 0:i.src);n&&this.load(n,this.options.peaks,this.options.duration)}initTimerEvents(){this.subscriptions.push(this.timer.on("tick",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),!0),this.emit("timeupdate",t),this.emit("audioprocess",t)})))}initPlayerEvents(){this.subscriptions.push(this.onMediaEvent("timeupdate",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),this.isPlaying()),this.emit("timeupdate",t)})),this.onMediaEvent("play",(()=>{this.emit("play"),this.timer.start()})),this.onMediaEvent("pause",(()=>{this.emit("pause"),this.timer.stop()})),this.onMediaEvent("emptied",(()=>{this.timer.stop()})),this.onMediaEvent("ended",(()=>{this.emit("finish")})),this.onMediaEvent("seeking",(()=>{this.emit("seeking",this.getCurrentTime())})))}initRendererEvents(){this.subscriptions.push(this.renderer.on("click",((t,e)=>{this.options.interact&&(this.seekTo(t),this.emit("interaction",t*this.getDuration()),this.emit("click",t,e))})),this.renderer.on("dblclick",((t,e)=>{this.emit("dblclick",t,e)})),this.renderer.on("scroll",((t,e)=>{const i=this.getDuration();this.emit("scroll",t*i,e*i)})),this.renderer.on("render",(()=>{this.emit("redraw")})));{let t;this.subscriptions.push(this.renderer.on("drag",(e=>{this.options.interact&&(this.renderer.renderProgress(e),clearTimeout(t),t=setTimeout((()=>{this.seekTo(e)}),this.isPlaying()?0:200),this.emit("interaction",e*this.getDuration()),this.emit("drag",e))})))}}initPlugins(){var t;(null===(t=this.options.plugins)||void 0===t?void 0:t.length)&&this.options.plugins.forEach((t=>{this.registerPlugin(t)}))}setOptions(t){this.options=Object.assign({},this.options,t),this.renderer.setOptions(this.options),t.audioRate&&this.setPlaybackRate(t.audioRate),null!=t.mediaControls&&(this.getMediaElement().controls=t.mediaControls)}registerPlugin(t){return t.init(this),this.plugins.push(t),this.subscriptions.push(t.once("destroy",(()=>{this.plugins=this.plugins.filter((e=>e!==t))}))),t}getWrapper(){return this.renderer.getWrapper()}getScroll(){return this.renderer.getScroll()}getActivePlugins(){return this.plugins}loadAudio(s,n,r,o){return t(this,void 0,void 0,(function*(){if(this.emit("load",s),this.isPlaying()&&this.pause(),this.decodedData=null,!n&&!r){const t=t=>this.emit("loading",t);n=yield i.fetchBlob(s,t,this.options.fetchParams)}if(this.setSrc(s,n),r)o=(yield Promise.resolve(o||this.getDuration()))||(yield new Promise((t=>{this.onceMediaEvent("loadedmetadata",(()=>t(this.getDuration())))})))||(yield Promise.resolve(0)),this.decodedData=e.createBuffer(r,o);else if(n){const t=yield n.arrayBuffer();this.decodedData=yield e.decode(t,this.options.sampleRate)}this.emit("decode",this.getDuration()),this.decodedData&&this.renderer.render(this.decodedData),this.emit("ready",this.getDuration())}))}load(e,i,s){return t(this,void 0,void 0,(function*(){yield this.loadAudio(e,void 0,i,s)}))}loadBlob(e,i,s){return t(this,void 0,void 0,(function*(){yield this.loadAudio("blob",e,i,s)}))}zoom(t){if(!this.decodedData)throw new Error("No audio loaded");this.renderer.zoom(t),this.emit("zoom",t)}getDecodedData(){return this.decodedData}exportPeaks({channels:t=1,maxLength:e=8e3,precision:i=1e4}={}){if(!this.decodedData)throw new Error("The audio has not been decoded yet");const s=Math.min(t,this.decodedData.numberOfChannels),n=[];for(let t=0;tt.destroy())),this.subscriptions.forEach((t=>t())),this.timer.destroy(),this.renderer.destroy(),super.destroy()}}export{h as default}; diff --git a/Kitodo/src/main/webapp/WEB-INF/resources/js/media_detail_audio_waveform.js b/Kitodo/src/main/webapp/WEB-INF/resources/js/media_detail_audio_waveform.js new file mode 100644 index 00000000000..72360c1cd27 --- /dev/null +++ b/Kitodo/src/main/webapp/WEB-INF/resources/js/media_detail_audio_waveform.js @@ -0,0 +1,118 @@ +/** + * (c) Kitodo. Key to digital objects e. V. + * + * This file is part of the Kitodo project. + * + * It is licensed under GNU General Public License version 3 or later. + * + * For the full copyright and license information, please read the + * GPL3-License.txt file that was distributed with this source code. + */ + +import WaveSurfer from './libs/wavesurfer/wavesurfer.esm.js.jsf'; + +class AudioWaveform { + #audioElement; + #wavesurfer; + #buildTimeout; + #loader; + #peaksCache = []; + + constructor() { + this.init(); + } + + init() { + let self = this; + this.#audioElement = document.querySelector('audio.mediaPreviewItem'); + if (this.#audioElement && this.#audioElement.getAttribute("data-audio-waveform") !== "initialized") { + this.#audioElement.setAttribute("data-audio-waveform", "initialized"); + + // add a loader to visualize loading process + this.#loader = document.createElement("div"); + this.#loader.innerHTML = ''; + this.#loader.classList.add('loader'); + this.#audioElement.parentNode.insertBefore(this.#loader, this.#audioElement); + + // when the user agent can play the media + this.#audioElement && this.#audioElement.addEventListener("canplay", () => { + // Prevent browser crashes during audio decoding when multiple rapid clicks, such as double-clicking, occur. + clearTimeout(this.#buildTimeout); + self.#buildTimeout = setTimeout(function() { + self.#build(); + }, 500); + }, {once: true}); + + } + } + + #build() { + let self = this; + // wavesurfer uses the 'src' attribute of the audio element, and we add this attribute based on the browser's current source selection + this.#audioElement.src = this.#audioElement.currentSrc; + + // get the media id from the source parameter + const urlParams = new URLSearchParams(this.#audioElement.src); + let mediaId = urlParams.get('mediaId'); + + let waveContainer = document.createElement("div"); + waveContainer.setAttribute("id", "wave-container"); + waveContainer.onclick = function () { + self.#wavesurfer.playPause(); + }; + waveContainer.style.width = "90%"; + waveContainer.style.display = "none"; + this.#audioElement.parentNode.insertBefore(waveContainer, this.#audioElement); + + this.#wavesurfer = WaveSurfer.create({ + container: document.getElementById(waveContainer.getAttribute("id")), + height: 100, + waveColor: "#f3f3f3", + progressColor: "#ff4e00", + cursorColor: "#ffffff", + media: this.#audioElement, + minPxPerSec: 0, + peaks: this.#peaksCache[mediaId] + }); + + this.#wavesurfer.on("decode", function () { + // cache peaks after when audio has been decoded + self.#peaksCache[mediaId] = self.#wavesurfer.getDecodedData().getChannelData(0); + }); + + this.#wavesurfer.on("ready", function () { + waveContainer.style.display = "block"; + self.#loader.style.display = "none"; + + let waveToolsContainer = document.getElementById("audioWaveformTools"); + const waveToolsSlider = waveToolsContainer.querySelector('input[type="range"]'); + + waveToolsSlider.addEventListener('input', (e) => { + const minPxPerSec = e.target.valueAsNumber; + self.#wavesurfer.zoom(minPxPerSec); + }); + + waveToolsContainer.querySelectorAll('input[type="checkbox"]').forEach((input) => { + input.onchange = (e) => { + self.#wavesurfer.setOptions({ + [input.value]: e.target.checked, + }); + } + }); + const jumpButtons = document.getElementsByClassName("audio-waveform-tools-jump-button"); + Array.from(jumpButtons).forEach(function (jumpButton) { + jumpButton.addEventListener('click', function () { + let jumpSeconds = parseInt(this.getAttribute("data-audio-waveform-tools-jump-seconds")); + self.#wavesurfer.setTime(self.#wavesurfer.getCurrentTime() + jumpSeconds); + }); + }); + }); + } + +} + +const audioWaveform= new AudioWaveform(); + +document.addEventListener("kitodo-metadataditor-mediaview-update", function () { + audioWaveform.init(); +}); diff --git a/Kitodo/src/main/webapp/WEB-INF/resources/js/metadata_editor.js b/Kitodo/src/main/webapp/WEB-INF/resources/js/metadata_editor.js index 476ad6b0009..e0922870cd9 100644 --- a/Kitodo/src/main/webapp/WEB-INF/resources/js/metadata_editor.js +++ b/Kitodo/src/main/webapp/WEB-INF/resources/js/metadata_editor.js @@ -433,6 +433,13 @@ metadataEditor.gallery = { } }, + mediaView: { + updateEventName : "kitodo-metadataditor-mediaview-update", + update(){ + document.dispatchEvent(new Event(this.updateEventName)); + } + }, + /** * Event handlers methods related to gallery stripes */ diff --git a/Kitodo/src/main/webapp/WEB-INF/resources/js/resize.js b/Kitodo/src/main/webapp/WEB-INF/resources/js/resize.js index 26a1f5f33b3..208bb40dcc1 100644 --- a/Kitodo/src/main/webapp/WEB-INF/resources/js/resize.js +++ b/Kitodo/src/main/webapp/WEB-INF/resources/js/resize.js @@ -8,7 +8,7 @@ * For the full copyright and license information, please read the * GPL3-License.txt file that was distributed with this source code. */ -/* globals PF */ +/* globals PF, metadataEditor */ // jshint unused:false var SEPARATOR_WIDTH = 3; @@ -523,6 +523,7 @@ function updateMetadataEditorView(showMetadataColumn) { expandThirdColumn(); scrollToSelectedThumbnail(); initializeImage(); + metadataEditor.gallery.mediaView.update(); scrollToSelectedTreeNode(); scrollToSelectedPaginationRow(); } diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/base/commentTooltip.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/base/commentTooltip.xhtml index 29b45260c51..be851259e9d 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/base/commentTooltip.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/base/commentTooltip.xhtml @@ -22,9 +22,8 @@ - diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/comments/comments.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/comments/comments.xhtml index 028edb7fc46..8dd0960de2d 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/comments/comments.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/comments/comments.xhtml @@ -19,11 +19,10 @@ - + - + @@ -43,6 +42,28 @@ title="#{msgs['dataEditor.comment.markCorrectionCommentAsSolved']}" action="#{CommentForm.solveProblem(item)}"> + + + + + diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/comments/editCommentDialog.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/comments/editCommentDialog.xhtml new file mode 100644 index 00000000000..a82f2189d0b --- /dev/null +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/comments/editCommentDialog.xhtml @@ -0,0 +1,58 @@ + + + + + + +

#{msgs.commentEdit}

+ + + + + + + + + + + +
+
+
+
diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/comments/newCommentDialog.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/comments/newCommentDialog.xhtml index 639ee5eda2a..d0d7f220a39 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/comments/newCommentDialog.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/comments/newCommentDialog.xhtml @@ -65,7 +65,9 @@ - + + #{msgs.correctionMessage} + - #{msgs.correctionMessageWrite} + #{msgs.correctionMessageWrite} diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/desktop/processesWidget.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/desktop/processesWidget.xhtml index 10fcc183949..0a92202c62a 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/desktop/processesWidget.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/desktop/processesWidget.xhtml @@ -39,11 +39,10 @@ width="85" headerText="#{msgs.comments}"> - - + diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/desktop/projectsWidget.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/desktop/projectsWidget.xhtml index 22e8b751c08..32d9827d084 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/desktop/projectsWidget.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/desktop/projectsWidget.xhtml @@ -19,6 +19,7 @@ diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/header/navigation.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/header/navigation.xhtml index 70ee5d2f314..94258e993cc 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/header/navigation.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/header/navigation.xhtml @@ -21,8 +21,11 @@