Skip to content

Commit 93c91ce

Browse files
authored
Merge pull request #728 from yermak/development
Language support and bug-fixes
2 parents 11f0c47 + 7803154 commit 93c91ce

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1500
-1842
lines changed

src/main/java/uk/yermak/audiobookconverter/AudiobookConverter.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
*/
44

55
import javafx.application.Application;
6-
import javafx.fxml.FXMLLoader;
76
import javafx.scene.Parent;
87
import javafx.scene.Scene;
98
import javafx.scene.control.Alert;
@@ -15,6 +14,7 @@
1514
import org.slf4j.Logger;
1615
import org.slf4j.LoggerFactory;
1716
import uk.yermak.audiobookconverter.fx.ConversionContext;
17+
import uk.yermak.audiobookconverter.fx.FilesController;
1818
import uk.yermak.audiobookconverter.fx.JfxEnv;
1919
import uk.yermak.audiobookconverter.fx.WizardDialog;
2020

@@ -90,12 +90,16 @@ public void start(Stage stage) {
9090

9191
logger.info("Initialising application");
9292

93-
Locale defaultLocale = Locale.getDefault();
94-
bundle = getBundleWithFallback(defaultLocale);
93+
Settings settings = Settings.loadSetting();
94+
Locale selectedLocale = Optional.ofNullable(settings.getLanguage())
95+
.filter(StringUtils::isNotBlank)
96+
.map(Locale::forLanguageTag)
97+
.orElse(Locale.getDefault());
98+
Locale.setDefault(selectedLocale);
99+
bundle = getBundleWithFallback(selectedLocale);
95100

96101
try {
97-
URL resource = AudiobookConverter.class.getResource("/uk/yermak/audiobookconverter/fx/fxml_converter.fxml");
98-
root = FXMLLoader.load(resource, bundle);
102+
root = new FilesController(bundle);
99103

100104
Scene scene = new Scene(root);
101105
stage.setTitle("AudioBookConverter-"+Version.getVersionString());
@@ -105,7 +109,6 @@ public void start(Stage stage) {
105109
stage.setMinWidth(primary.getVisualBounds().getWidth() * 0.4);
106110
env = new JfxEnv(scene, getHostServices());
107111

108-
Settings settings = Settings.loadSetting();
109112
boolean dark = settings.isDarkMode();
110113
if (dark) {
111114
env.setDarkMode(dark);

src/main/java/uk/yermak/audiobookconverter/DurationVerifier.java

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,12 @@
44
import net.bramp.ffmpeg.FFprobe;
55
import net.bramp.ffmpeg.probe.FFmpegProbeResult;
66
import net.bramp.ffmpeg.probe.FFmpegStream;
7-
import org.apache.commons.io.output.ByteArrayOutputStream;
8-
import org.apache.commons.lang3.StringUtils;
97
import org.slf4j.Logger;
108
import org.slf4j.LoggerFactory;
119
import uk.yermak.audiobookconverter.book.MediaInfo;
1210

1311
import java.io.IOException;
1412
import java.lang.invoke.MethodHandles;
15-
import java.nio.charset.Charset;
1613
import java.util.List;
1714
import java.util.Set;
1815

@@ -22,50 +19,6 @@
2219
public class DurationVerifier {
2320
final static Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
2421

25-
26-
static long parseDuration(String info) {
27-
String[] lines = StringUtils.split(info, "\n");
28-
for (String line : lines) {
29-
if (StringUtils.isNotEmpty(line)) {
30-
String[] columns = StringUtils.split(line, ",");
31-
if (StringUtils.contains(columns[0], "audio")) {
32-
for (int j = 1, columnsLength = columns.length; j < columnsLength; j++) {
33-
String column = columns[j];
34-
int k;
35-
if ((k = column.indexOf(" sec")) != -1) {
36-
String substring = column.substring(1, k);
37-
return (long) (Double.parseDouble(substring) * 1000);
38-
}
39-
}
40-
}
41-
}
42-
}
43-
return 0;
44-
}
45-
46-
public static void mp4v2UpdateDuration(MediaInfo mediaInfo, String outputFileName) throws IOException {
47-
Process process = null;
48-
try {
49-
ProcessBuilder infoProcessBuilder = new ProcessBuilder(Platform.MP4INFO, outputFileName);
50-
process = infoProcessBuilder.start();
51-
ByteArrayOutputStream out = new ByteArrayOutputStream();
52-
StreamCopier.copy(process.getInputStream(), out);
53-
StreamCopier.copy(process.getErrorStream(), System.err);
54-
process.waitFor();
55-
String info = out.toString(Charset.defaultCharset());
56-
long duration = parseDuration(info);
57-
if (duration != 0) {
58-
mediaInfo.setDuration(duration);
59-
} else {
60-
logger.warn("Failed to detect actual duration for '" + mediaInfo.getFileName() + "' in file: '" + outputFileName + "', extracted info: " + info);
61-
}
62-
} catch (InterruptedException e) {
63-
logger.error(e.getMessage(), e);
64-
} finally {
65-
Utils.closeSilently(process);
66-
}
67-
}
68-
6922
public static void ffMpegUpdateDuration(MediaInfo mediaInfo, String outputFileName) throws IOException {
7023
final Set<String> AUDIO_CODECS = ImmutableSet.of("mp3", "aac", "wmav2", "flac", "alac", "vorbis", "opus");
7124
FFprobe ffprobe = new FFprobe(Platform.FFPROBE);

src/main/java/uk/yermak/audiobookconverter/FFMpegConcatenator.java

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package uk.yermak.audiobookconverter;
22

3+
import net.bramp.ffmpeg.progress.Progress;
34
import net.bramp.ffmpeg.progress.ProgressParser;
45
import net.bramp.ffmpeg.progress.TcpProgressParser;
56
import org.apache.commons.io.FileUtils;
@@ -13,7 +14,6 @@
1314
import java.io.IOException;
1415
import java.lang.invoke.MethodHandles;
1516
import java.net.URISyntaxException;
16-
import java.util.ArrayList;
1717
import java.util.List;
1818
import java.util.Objects;
1919
import java.util.concurrent.TimeUnit;
@@ -30,8 +30,6 @@ public class FFMpegConcatenator {
3030

3131
private List<MediaInfo> media;
3232
private final ProgressCallback callback;
33-
private ProgressParser progressParser;
34-
private List<String> tmpFiles = new ArrayList<>();
3533

3634
public FFMpegConcatenator(ConversionJob conversionJob, String outputFileName, List<MediaInfo> media, ProgressCallback callback) {
3735
this.conversionJob = conversionJob;
@@ -52,20 +50,17 @@ public void concat() throws IOException, InterruptedException {
5250
String fileListFileName = prepareFiles(conversionJob.getConversionGroup().getGroupId(), media, conversionJob.getConversionGroup().getWorkfileExtension(), outputFileName).getAbsolutePath();
5351

5452
while (ProgressStatus.PAUSED.equals(conversionJob.getStatus())) Thread.sleep(1000);
55-
callback.reset();
56-
try {
57-
progressParser = new TcpProgressParser(progress -> callback.converted(progress.out_time_ns / 1000000, progress.total_size));
58-
progressParser.start();
59-
} catch (URISyntaxException e) {
60-
}
53+
callback.reset(false);
6154

6255
Process process = null;
63-
try {
56+
57+
try(ProgressParser progressParser = new TcpProgressParser(this::progress)){
58+
progressParser.start();
59+
6460
OutputParameters outputParameters = conversionJob.getConversionGroup().getOutputParameters();
6561
List<String> concatOptions = outputParameters.getFormat().getConcatOptions(fileListFileName, outputFileName, progressParser.getUri().toString(), conversionJob);
6662

6763
logger.info("Starting concat with options {}", String.join(" ", concatOptions));
68-
6964
//using custom processes for Windows here - Runtime.exec() due to JDK specific way of interpreting quoted arguments in ProcessBuilder https://bugs.openjdk.java.net/browse/JDK-8131908
7065
process = Platform.current.createProcess(concatOptions);
7166

@@ -88,14 +83,20 @@ public void concat() throws IOException, InterruptedException {
8883
if (!new File(outputFileName).exists()) {
8984
throw new ConversionException("Concatenation failed, no output file:" + out, new Error(err.toString()));
9085
}
86+
} catch (URISyntaxException e) {
87+
logger.error("Error during concatination of files:", e);
88+
throw new RuntimeException(e);
9189
} catch (Exception e) {
9290
logger.error("Error during concatination of files:", e);
9391
throw new RuntimeException(e);
9492
} finally {
9593
Utils.closeSilently(process);
96-
Utils.closeSilently(progressParser);
9794
media.forEach(mediaInfo -> FileUtils.deleteQuietly(new File(Utils.getTmp(conversionJob.getConversionGroup().getGroupId(), mediaInfo.getUID(), conversionJob.getConversionGroup().getWorkfileExtension()))));
9895
FileUtils.deleteQuietly(new File(fileListFileName));
9996
}
10097
}
98+
99+
private void progress(Progress progress) {
100+
callback.converted(progress.out_time_ns / 1000000, progress.total_size);
101+
}
101102
}

src/main/java/uk/yermak/audiobookconverter/FFMpegNativeConverter.java

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package uk.yermak.audiobookconverter;
22

3+
import net.bramp.ffmpeg.progress.Progress;
34
import net.bramp.ffmpeg.progress.ProgressParser;
45
import net.bramp.ffmpeg.progress.TcpProgressParser;
56
import org.slf4j.Logger;
@@ -36,18 +37,10 @@ public FFMpegNativeConverter(ConversionJob conversionJob, MediaInfo mediaInfo, S
3637

3738
@Override
3839
public String call() throws Exception {
39-
try {
40-
if (conversionJob.getStatus().isOver()) return null;
41-
while (ProgressStatus.PAUSED.equals(conversionJob.getStatus())) Thread.sleep(1000);
40+
if (conversionJob.getStatus().isOver()) return null;
41+
while (ProgressStatus.PAUSED.equals(conversionJob.getStatus())) Thread.sleep(1000);
4242

43-
progressParser = new TcpProgressParser(progress -> {
44-
callback.converted(progress.out_time_ns / 1000000, progress.total_size);
45-
// logger.debug("Completed conversion: {} from {}", progress.out_time_ns / 1000000, progress.total_size);
46-
if (progress.isEnd()) {
47-
callback.converted(progress.out_time_ns / 1000000, progress.total_size);
48-
callback.completedConversion();
49-
}
50-
});
43+
try (ProgressParser progressParser = new TcpProgressParser(this::progress)) {
5144
progressParser.start();
5245

5346
ProcessBuilder ffmpegProcessBuilder;
@@ -74,10 +67,9 @@ public String call() throws Exception {
7467
logger.debug("ffmpeg out: {}", out);
7568
logger.warn("ffmpeg err: {}", err);
7669

77-
7870
if (process.exitValue() != 0) {
7971
logger.error("Converstion failed: " + err);
80-
throw new RuntimeException("Converstion failed: "+ err);
72+
throw new RuntimeException("Converstion failed: " + err);
8173
} else {
8274
DurationVerifier.ffMpegUpdateDuration(mediaInfo, outputFileName);
8375
}
@@ -88,9 +80,16 @@ public String call() throws Exception {
8880
throw new RuntimeException(e);
8981
} finally {
9082
Utils.closeSilently(process);
91-
Utils.closeSilently(progressParser);
9283
}
9384
}
9485

9586

87+
private void progress(Progress progress) {
88+
callback.converted(progress.out_time_ns / 1000000, progress.total_size);
89+
// logger.debug("Completed conversion: {} from {}", progress.out_time_ns / 1000000, progress.total_size);
90+
if (progress.isEnd()) {
91+
callback.converted(progress.out_time_ns / 1000000, progress.total_size);
92+
callback.completedConversion();
93+
}
94+
}
9695
}
Lines changed: 45 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package uk.yermak.audiobookconverter;
22

3+
import net.bramp.ffmpeg.progress.Progress;
34
import net.bramp.ffmpeg.progress.ProgressParser;
45
import net.bramp.ffmpeg.progress.TcpProgressParser;
56
import org.slf4j.Logger;
@@ -8,7 +9,6 @@
89

910
import java.io.ByteArrayOutputStream;
1011
import java.io.File;
11-
import java.io.IOException;
1212
import java.lang.invoke.MethodHandles;
1313
import java.net.URISyntaxException;
1414
import java.util.ResourceBundle;
@@ -23,7 +23,6 @@ public class FFMpegOptimizer {
2323
private final String outputFileName;
2424
private ProgressCallback callback;
2525

26-
private ProgressParser progressParser;
2726

2827
public FFMpegOptimizer(ConversionJob conversionJob, String tempFile, String outputFileName, ProgressCallback callback, ResourceBundle resources) {
2928
this.conversionJob = conversionJob;
@@ -34,59 +33,63 @@ public FFMpegOptimizer(ConversionJob conversionJob, String tempFile, String outp
3433
}
3534

3635

37-
String optimize() throws InterruptedException, IOException {
36+
String optimize() throws InterruptedException {
3837
while (ProgressStatus.PAUSED.equals(conversionJob.getStatus())) Thread.sleep(1000);
39-
callback.reset();
40-
callback.setState(resources.getString("progress.state.optimizing"));
41-
try {
42-
progressParser = new TcpProgressParser(progress -> {
43-
callback.converted(progress.out_time_ns / 1000000, progress.total_size);
44-
});
45-
progressParser.start();
46-
} catch (URISyntaxException e) {
38+
39+
if (!conversionJob.getConversionGroup().getOutputParameters().getFormat().mp4Compatible()) {
40+
return outputFileName;
4741
}
4842

43+
callback.reset(true);
44+
callback.setState(resources.getString("progress.state.optimizing"));
4945
Process process = null;
50-
try {
46+
try (ProgressParser progressParser = new TcpProgressParser(this::progress)) {
47+
48+
progressParser.start();
5149

5250
String optimizedFile = Utils.getTmp(conversionJob.getConversionGroup().getGroupId(), outputFileName.hashCode() + 1, conversionJob.getConversionGroup().getWorkfileExtension());
53-
if (conversionJob.getConversionGroup().getOutputParameters().getFormat().mp4Compatible()) {
54-
MP4Format format = (MP4Format) conversionJob.getConversionGroup().getOutputParameters().getFormat();
55-
56-
boolean hasPosters = !conversionJob.getConversionGroup().getPosters().isEmpty();
57-
String[] optimize = format.getOptimizeOptions(tempFile, progressParser, optimizedFile, hasPosters);
58-
logger.debug("Starting optimisation with options {}", String.join(" ", optimize));
59-
60-
ProcessBuilder pb = new ProcessBuilder(optimize);
61-
process = pb.start();
62-
63-
ByteArrayOutputStream out = new ByteArrayOutputStream();
64-
StreamCopier.copy(process.getInputStream(), out);
65-
ByteArrayOutputStream err = new ByteArrayOutputStream();
66-
StreamCopier.copy(process.getErrorStream(), err);
67-
68-
boolean finished = false;
69-
while (!conversionJob.getStatus().isOver() && !finished) {
70-
finished = process.waitFor(500, TimeUnit.MILLISECONDS);
71-
}
72-
logger.debug("Optimize Out: {}", out);
73-
logger.error("Optimize Error: {}", err);
74-
75-
if (process.exitValue() != 0) {
76-
throw new ConversionException("Optimisation exit code " + process.exitValue() + "!=0", new Error(err.toString()));
77-
}
78-
79-
if (!new File(optimizedFile).exists()) {
80-
throw new ConversionException("Optimisation failed, no output file:" + out, new Error(err.toString()));
81-
}
51+
52+
MP4Format format = (MP4Format) conversionJob.getConversionGroup().getOutputParameters().getFormat();
53+
54+
boolean hasPosters = !conversionJob.getConversionGroup().getPosters().isEmpty();
55+
String[] optimize = format.getOptimizeOptions(tempFile, progressParser, optimizedFile, hasPosters);
56+
logger.debug("Starting optimisation with options {}", String.join(" ", optimize));
57+
58+
ProcessBuilder pb = new ProcessBuilder(optimize);
59+
process = pb.start();
60+
61+
ByteArrayOutputStream out = new ByteArrayOutputStream();
62+
StreamCopier.copy(process.getInputStream(), out);
63+
ByteArrayOutputStream err = new ByteArrayOutputStream();
64+
StreamCopier.copy(process.getErrorStream(), err);
65+
66+
boolean finished = false;
67+
while (!conversionJob.getStatus().isOver() && !finished) {
68+
finished = process.waitFor(500, TimeUnit.MILLISECONDS);
69+
}
70+
logger.debug("Optimize Out: {}", out);
71+
logger.error("Optimize Error: {}", err);
72+
73+
if (process.exitValue() != 0) {
74+
throw new ConversionException("Optimisation exit code " + process.exitValue() + "!=0", new Error(err.toString()));
75+
}
76+
77+
if (!new File(optimizedFile).exists()) {
78+
throw new ConversionException("Optimisation failed, no output file:" + out, new Error(err.toString()));
8279
}
8380
return optimizedFile;
81+
} catch (URISyntaxException e) {
82+
logger.error("Error during optimisation of resulting file:", e);
83+
throw new RuntimeException(e);
8484
} catch (Exception e) {
8585
logger.error("Error during optimisation of resulting file:", e);
8686
throw new RuntimeException(e);
8787
} finally {
8888
Utils.closeSilently(process);
89-
Utils.closeSilently(progressParser);
9089
}
9190
}
91+
92+
private void progress(Progress progress) {
93+
callback.converted(progress.out_time_ns / 1000000, progress.total_size);
94+
}
9295
}

src/main/java/uk/yermak/audiobookconverter/Mp4v2ArtBuilder.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.io.File;
99
import java.lang.invoke.MethodHandles;
1010
import java.util.List;
11+
import java.util.ResourceBundle;
1112
import java.util.concurrent.TimeUnit;
1213

1314
/**
@@ -28,8 +29,9 @@ public Mp4v2ArtBuilder(ConversionJob conversionJob, ProgressCallback progressCal
2829
public void coverArt(String outputFileName) {
2930
List<ArtWork> posters = conversionJob.getConversionGroup().getPosters();
3031
if (posters.isEmpty()) return;
31-
progressCallback.reset();
32-
progressCallback.setState("Adding artwork...");
32+
progressCallback.reset(true);
33+
ResourceBundle resources = AudiobookConverter.getBundle();
34+
progressCallback.setState(resources.getString("progress.state.addingArtwork"));
3335
long duration = conversionJob.getConvertable().getDuration();
3436
long step = duration / posters.size();
3537
for (int i = 0; i < posters.size(); i++) {

0 commit comments

Comments
 (0)