19 package org.sleuthkit.autopsy.texttranslation.ui;
21 import com.google.common.collect.Lists;
22 import com.google.common.util.concurrent.ThreadFactoryBuilder;
23 import java.awt.Component;
24 import java.awt.ComponentOrientation;
26 import java.awt.event.ActionEvent;
27 import java.awt.event.ActionListener;
28 import java.io.IOException;
29 import java.io.Reader;
30 import java.util.Arrays;
31 import java.util.concurrent.CancellationException;
32 import java.util.concurrent.ExecutionException;
33 import java.util.concurrent.ExecutorService;
34 import java.util.concurrent.Executors;
35 import java.util.concurrent.ThreadFactory;
36 import org.openide.nodes.Node;
37 import org.openide.util.lookup.ServiceProvider;
40 import javax.swing.SwingWorker;
41 import org.openide.util.Lookup;
42 import org.openide.util.NbBundle;
43 import org.openide.util.lookup.Lookups;
54 import java.util.List;
55 import java.util.logging.Level;
56 import javax.swing.SwingUtilities;
64 @ServiceProvider(service =
TextViewer.class, position = 4)
69 private static final boolean OCR_ENABLED =
true;
70 private static final boolean OCR_DISABLED =
false;
71 private static final int MAX_EXTRACT_SIZE_BYTES = 25600;
75 private volatile Node
node;
77 private final ThreadFactory translationThreadFactory
78 =
new ThreadFactoryBuilder().setNameFormat(
"translation-content-viewer-%d").build();
79 private final ExecutorService executorService = Executors.newSingleThreadExecutor(translationThreadFactory);
82 "TranslatedTextViewer.maxPayloadSize=Up to the first %dKB of text will be translated"
88 panel.addDisplayTextActionListener(displayDropDownListener);
92 if (source instanceof AbstractFile) {
93 boolean isImage = ((AbstractFile) source).getMIMEType().toLowerCase().startsWith(
"image/");
95 panel.enableOCRSelection(OCR_ENABLED);
96 panel.addLanguagePackNames(INSTALLED_LANGUAGE_PACKS);
101 panel.setWarningLabelMsg(String.format(Bundle.TranslatedTextViewer_maxPayloadSize(), payloadMaxInKB));
107 @NbBundle.Messages({
"TranslatedTextViewer.title=Translation"})
110 return Bundle.TranslatedTextViewer_title();
113 @NbBundle.Messages({
"TranslatedTextViewer.toolTip=Displays translated file text."})
116 return Bundle.TranslatedTextViewer_toolTip();
133 if (backgroundTask != null) {
134 backgroundTask.cancel(
true);
136 backgroundTask = null;
149 AbstractFile file = node.getLookup().lookup(AbstractFile.class);
165 private final AbstractFile
file;
170 this.translateText = translateText;
174 "TranslatedContentViewer.extractingText=Extracting text, please wait...",
175 "TranslatedContentViewer.translatingText=Translating text, please wait...",
176 "# {0} - exception message",
"TranslatedContentViewer.errorExtractingText=An error occurred while extracting the text ({0}).",
177 "TranslatedContentViewer.fileHasNoText=File has no text.",
178 "TranslatedContentViewer.noServiceProvider=The machine translation software was not found.",
179 "# {0} - exception message",
"TranslatedContentViewer.translationException=An error occurred while translating the text ({0})."
183 if (this.isCancelled()) {
184 throw new InterruptedException();
187 SwingUtilities.invokeLater(() -> {
188 panel.
display(Bundle.TranslatedContentViewer_extractingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
192 fileText = getFileText(file);
194 logger.log(Level.WARNING, String.format(
"Error extracting text for file %s (objId=%d)", file.getName(), file.getId()), ex);
195 return Bundle.TranslatedContentViewer_errorExtractingText(ex.getMessage());
198 if (this.isCancelled()) {
199 throw new InterruptedException();
202 if (fileText == null || fileText.isEmpty()) {
203 return Bundle.TranslatedContentViewer_fileHasNoText();
206 if (!this.translateText) {
210 SwingUtilities.invokeLater(() -> {
211 panel.
display(Bundle.TranslatedContentViewer_translatingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
215 translation = translate(fileText);
217 logger.log(Level.WARNING, String.format(
"Error translating text for file %s (objId=%d)", file.getName(), file.getId()), ex);
218 translation = Bundle.TranslatedContentViewer_noServiceProvider();
220 logger.log(Level.WARNING, String.format(
"Error translating text for file %s (objId=%d)", file.getName(), file.getId()), ex);
221 translation = Bundle.TranslatedContentViewer_translationException(ex.getMessage());
224 if (this.isCancelled()) {
225 throw new InterruptedException();
234 String result =
get();
235 if (this.isCancelled()) {
236 throw new InterruptedException();
238 int len = result.length();
239 int maxOrientChars = Math.min(len, 1024);
240 String orientDetectSubstring = result.substring(0, maxOrientChars);
242 panel.
display(result, orientation, Font.PLAIN);
244 }
catch (InterruptedException | CancellationException ignored) {
246 }
catch (ExecutionException ex) {
247 logger.log(Level.WARNING, String.format(
"Error occurred during background task execution for file %s (objId=%d)", file.getName(), file.getId()), ex);
248 panel.
display(Bundle.TranslatedContentViewer_translationException(ex.getMessage()), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
260 "TranslatedContentViewer.emptyTranslation=The machine translation software did not return any text."
264 String translatedResult = translatorInstance.
translate(input);
265 if (translatedResult.isEmpty()) {
266 return Bundle.TranslatedContentViewer_emptyTranslation();
268 return translatedResult;
286 final boolean isImage = file.getMIMEType().toLowerCase().startsWith(
"image/");
289 result = extractText(file, OCR_ENABLED);
291 result = extractText(file, OCR_DISABLED);
295 byte[] resultInUTF8Bytes = result.getBytes(
"UTF8");
296 byte[] trimToArraySize = Arrays.copyOfRange(resultInUTF8Bytes, 0,
297 Math.min(resultInUTF8Bytes.length, MAX_EXTRACT_SIZE_BYTES));
298 return new String(trimToArraySize,
"UTF-8");
315 Reader textExtractor = getTextExtractor(source, ocrEnabled);
317 char[] cbuf =
new char[8096];
318 StringBuilder textBuilder =
new StringBuilder();
325 while ((read = textExtractor.read(cbuf)) != -1) {
326 if (this.isCancelled()) {
327 throw new InterruptedException();
332 int bytesLeft = MAX_EXTRACT_SIZE_BYTES - bytesRead;
334 if (bytesLeft < read) {
335 textBuilder.append(cbuf, 0, bytesLeft);
336 return textBuilder.toString();
339 textBuilder.append(cbuf, 0, read);
343 return textBuilder.toString();
361 Lookup context = null;
367 String ocrSelection = panel.getSelectedOcrLanguagePack();
368 if (!ocrSelection.isEmpty()) {
375 context = Lookups.fixed(imageConfig, terminator);
395 abstract String getSelection();
399 String selection = getSelection();
400 if (!selection.equals(currentSelection)) {
401 currentSelection = selection;
403 if (backgroundTask != null && !backgroundTask.isDone()) {
404 backgroundTask.cancel(
true);
407 AbstractFile file = node.getLookup().lookup(AbstractFile.class);
408 String textDisplaySelection = panel.getDisplayDropDownSelection();
409 boolean translateText = !textDisplaySelection.equals(DisplayDropdownOptions.ORIGINAL_TEXT.toString());
414 executorService.execute(backgroundTask);
425 String getSelection() {
426 return panel.getDisplayDropDownSelection();
436 String getSelection() {
437 return panel.getSelectedOcrLanguagePack();
void setNode(final Node node)
volatile ExtractAndTranslateTextTask backgroundTask
synchronized int getMaxTextChars()
final boolean translateText
static Content getDefaultContent(Node node)
synchronized String translate(String input)
String extractText(AbstractFile source, boolean ocrEnabled)
String getFileText(AbstractFile file)
void display(String text, ComponentOrientation direction, int font)
ExtractAndTranslateTextTask(AbstractFile file, boolean translateText)
Reader getTextExtractor(AbstractFile file, boolean ocrEnabled)
boolean isSupported(Node node)
final void actionPerformed(ActionEvent e)
static TextTranslationService getInstance()
String translate(String input)
synchronized boolean hasProvider()
synchronized static Logger getLogger(String name)
static ComponentOrientation getTextDirection(String text)
TextViewer createInstance()
int isPreferred(Node node)