source: josm/trunk/src/org/openstreetmap/josm/gui/layer/gpx/ImportAudioAction.java@ 12846

Last change on this file since 12846 was 12846, checked in by bastiK, 7 years ago

see #15229 - use Config.getPref() wherever possible

  • Property svn:eol-style set to native
File size: 13.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer.gpx;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.event.ActionEvent;
8import java.io.File;
9import java.net.URL;
10import java.util.ArrayList;
11import java.util.Arrays;
12import java.util.Collection;
13import java.util.Comparator;
14
15import javax.swing.AbstractAction;
16import javax.swing.JFileChooser;
17import javax.swing.JOptionPane;
18import javax.swing.filechooser.FileFilter;
19
20import org.openstreetmap.josm.Main;
21import org.openstreetmap.josm.actions.DiskAccessAction;
22import org.openstreetmap.josm.data.gpx.GpxConstants;
23import org.openstreetmap.josm.data.gpx.GpxData;
24import org.openstreetmap.josm.data.gpx.GpxTrack;
25import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
26import org.openstreetmap.josm.data.gpx.WayPoint;
27import org.openstreetmap.josm.gui.HelpAwareOptionPane;
28import org.openstreetmap.josm.gui.MainApplication;
29import org.openstreetmap.josm.gui.layer.GpxLayer;
30import org.openstreetmap.josm.gui.layer.markerlayer.AudioMarker;
31import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
32import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
33import org.openstreetmap.josm.io.audio.AudioUtil;
34import org.openstreetmap.josm.spi.preferences.Config;
35import org.openstreetmap.josm.tools.ImageProvider;
36import org.openstreetmap.josm.tools.Utils;
37
38/**
39 * Import audio files into a GPX layer to enable audio playback functions.
40 * @since 5715
41 */
42public class ImportAudioAction extends AbstractAction {
43 private final transient GpxLayer layer;
44
45 /**
46 * Audio file filter.
47 * @since 12328
48 */
49 public static final class AudioFileFilter extends FileFilter {
50 @Override
51 public boolean accept(File f) {
52 return f.isDirectory() || Utils.hasExtension(f, "wav", "mp3", "aac", "aif", "aiff");
53 }
54
55 @Override
56 public String getDescription() {
57 return tr("Audio files (*.wav, *.mp3, *.aac, *.aif, *.aiff)");
58 }
59 }
60
61 private static class Markers {
62 public boolean timedMarkersOmitted;
63 public boolean untimedMarkersOmitted;
64 }
65
66 /**
67 * Constructs a new {@code ImportAudioAction}.
68 * @param layer The associated GPX layer
69 */
70 public ImportAudioAction(final GpxLayer layer) {
71 super(tr("Import Audio"), ImageProvider.get("importaudio"));
72 this.layer = layer;
73 putValue("help", ht("/Action/ImportAudio"));
74 }
75
76 private static void warnCantImportIntoServerLayer(GpxLayer layer) {
77 String msg = tr("<html>The data in the GPX layer ''{0}'' has been downloaded from the server.<br>" +
78 "Because its way points do not include a timestamp we cannot correlate them with audio data.</html>",
79 Utils.escapeReservedCharactersHTML(layer.getName()));
80 HelpAwareOptionPane.showOptionDialog(Main.parent, msg, tr("Import not possible"),
81 JOptionPane.WARNING_MESSAGE, ht("/Action/ImportAudio#CantImportIntoGpxLayerFromServer"));
82 }
83
84 @Override
85 public void actionPerformed(ActionEvent e) {
86 if (layer.data.fromServer) {
87 warnCantImportIntoServerLayer(layer);
88 return;
89 }
90 AbstractFileChooser fc = DiskAccessAction.createAndOpenFileChooser(true, true, null, new AudioFileFilter(),
91 JFileChooser.FILES_ONLY, "markers.lastaudiodirectory");
92 if (fc != null) {
93 File[] sel = fc.getSelectedFiles();
94 // sort files in increasing order of timestamp (this is the end time, but so
95 // long as they don't overlap, that's fine)
96 if (sel.length > 1) {
97 Arrays.sort(sel, Comparator.comparingLong(File::lastModified));
98 }
99 StringBuilder names = new StringBuilder();
100 for (File file : sel) {
101 if (names.length() == 0) {
102 names.append(" (");
103 } else {
104 names.append(", ");
105 }
106 names.append(file.getName());
107 }
108 if (names.length() > 0) {
109 names.append(')');
110 }
111 MarkerLayer ml = new MarkerLayer(new GpxData(),
112 tr("Audio markers from {0}", layer.getName()) + names, layer.getAssociatedFile(), layer);
113 double firstStartTime = sel[0].lastModified() / 1000.0 - AudioUtil.getCalibratedDuration(sel[0]);
114 Markers m = new Markers();
115 for (File file : sel) {
116 importAudio(file, ml, firstStartTime, m);
117 }
118 MainApplication.getLayerManager().addLayer(ml);
119 MainApplication.getMap().repaint();
120 }
121 }
122
123 /**
124 * Makes a new marker layer derived from this GpxLayer containing at least one audio marker
125 * which the given audio file is associated with. Markers are derived from the following (a)
126 * explict waypoints in the GPX layer, or (b) named trackpoints in the GPX layer, or (d)
127 * timestamp on the audio file (e) (in future) voice recognised markers in the sound recording (f)
128 * a single marker at the beginning of the track
129 * @param audioFile the file to be associated with the markers in the new marker layer
130 * @param ml marker layer
131 * @param firstStartTime first start time in milliseconds, used for (d)
132 * @param markers keeps track of warning messages to avoid repeated warnings
133 */
134 private void importAudio(File audioFile, MarkerLayer ml, double firstStartTime, Markers markers) {
135 URL url = Utils.fileToURL(audioFile);
136 boolean hasTracks = layer.data.tracks != null && !layer.data.tracks.isEmpty();
137 boolean hasWaypoints = layer.data.waypoints != null && !layer.data.waypoints.isEmpty();
138 Collection<WayPoint> waypoints = new ArrayList<>();
139 boolean timedMarkersOmitted = false;
140 boolean untimedMarkersOmitted = false;
141 double snapDistance = Config.getPref().getDouble("marker.audiofromuntimedwaypoints.distance", 1.0e-3);
142 // about 25 m
143 WayPoint wayPointFromTimeStamp = null;
144
145 // determine time of first point in track
146 double firstTime = -1.0;
147 if (hasTracks) {
148 for (GpxTrack track : layer.data.tracks) {
149 for (GpxTrackSegment seg : track.getSegments()) {
150 for (WayPoint w : seg.getWayPoints()) {
151 firstTime = w.time;
152 break;
153 }
154 if (firstTime >= 0.0) {
155 break;
156 }
157 }
158 if (firstTime >= 0.0) {
159 break;
160 }
161 }
162 }
163 if (firstTime < 0.0) {
164 JOptionPane.showMessageDialog(
165 Main.parent,
166 tr("No GPX track available in layer to associate audio with."),
167 tr("Error"),
168 JOptionPane.ERROR_MESSAGE
169 );
170 return;
171 }
172
173 // (a) try explicit timestamped waypoints - unless suppressed
174 if (hasWaypoints && Config.getPref().getBoolean("marker.audiofromexplicitwaypoints", true)) {
175 for (WayPoint w : layer.data.waypoints) {
176 if (w.time > firstTime) {
177 waypoints.add(w);
178 } else if (w.time > 0.0) {
179 timedMarkersOmitted = true;
180 }
181 }
182 }
183
184 // (b) try explicit waypoints without timestamps - unless suppressed
185 if (hasWaypoints && Config.getPref().getBoolean("marker.audiofromuntimedwaypoints", true)) {
186 for (WayPoint w : layer.data.waypoints) {
187 if (waypoints.contains(w)) {
188 continue;
189 }
190 WayPoint wNear = layer.data.nearestPointOnTrack(w.getEastNorth(Main.getProjection()), snapDistance);
191 if (wNear != null) {
192 WayPoint wc = new WayPoint(w.getCoor());
193 wc.time = wNear.time;
194 if (w.attr.containsKey(GpxConstants.GPX_NAME)) {
195 wc.put(GpxConstants.GPX_NAME, w.getString(GpxConstants.GPX_NAME));
196 }
197 waypoints.add(wc);
198 } else {
199 untimedMarkersOmitted = true;
200 }
201 }
202 }
203
204 // (c) use explicitly named track points, again unless suppressed
205 if (layer.data.tracks != null && Config.getPref().getBoolean("marker.audiofromnamedtrackpoints", false)
206 && !layer.data.tracks.isEmpty()) {
207 for (GpxTrack track : layer.data.tracks) {
208 for (GpxTrackSegment seg : track.getSegments()) {
209 for (WayPoint w : seg.getWayPoints()) {
210 if (w.attr.containsKey(GpxConstants.GPX_NAME) || w.attr.containsKey(GpxConstants.GPX_DESC)) {
211 waypoints.add(w);
212 }
213 }
214 }
215 }
216 }
217
218 // (d) use timestamp of file as location on track
219 if (hasTracks && Config.getPref().getBoolean("marker.audiofromwavtimestamps", false)) {
220 double lastModified = audioFile.lastModified() / 1000.0; // lastModified is in milliseconds
221 double duration = AudioUtil.getCalibratedDuration(audioFile);
222 double startTime = lastModified - duration;
223 startTime = firstStartTime + (startTime - firstStartTime)
224 / Config.getPref().getDouble("audio.calibration", 1.0 /* default, ratio */);
225 WayPoint w1 = null;
226 WayPoint w2 = null;
227
228 for (GpxTrack track : layer.data.tracks) {
229 for (GpxTrackSegment seg : track.getSegments()) {
230 for (WayPoint w : seg.getWayPoints()) {
231 if (startTime < w.time) {
232 w2 = w;
233 break;
234 }
235 w1 = w;
236 }
237 if (w2 != null) {
238 break;
239 }
240 }
241 }
242
243 if (w1 == null || w2 == null) {
244 timedMarkersOmitted = true;
245 } else {
246 wayPointFromTimeStamp = new WayPoint(w1.getCoor().interpolate(w2.getCoor(),
247 (startTime - w1.time) / (w2.time - w1.time)));
248 wayPointFromTimeStamp.time = startTime;
249 String name = audioFile.getName();
250 int dot = name.lastIndexOf('.');
251 if (dot > 0) {
252 name = name.substring(0, dot);
253 }
254 wayPointFromTimeStamp.put(GpxConstants.GPX_NAME, name);
255 waypoints.add(wayPointFromTimeStamp);
256 }
257 }
258
259 // (e) analyse audio for spoken markers here, in due course
260
261 // (f) simply add a single marker at the start of the track
262 if ((Config.getPref().getBoolean("marker.audiofromstart") || waypoints.isEmpty()) && hasTracks) {
263 boolean gotOne = false;
264 for (GpxTrack track : layer.data.tracks) {
265 for (GpxTrackSegment seg : track.getSegments()) {
266 for (WayPoint w : seg.getWayPoints()) {
267 WayPoint wStart = new WayPoint(w.getCoor());
268 wStart.put(GpxConstants.GPX_NAME, "start");
269 wStart.time = w.time;
270 waypoints.add(wStart);
271 gotOne = true;
272 break;
273 }
274 if (gotOne) {
275 break;
276 }
277 }
278 if (gotOne) {
279 break;
280 }
281 }
282 }
283
284 // we must have got at least one waypoint now
285 ((ArrayList<WayPoint>) waypoints).sort(Comparator.comparingDouble(o -> o.time));
286
287 firstTime = -1.0; // this time of the first waypoint, not first trackpoint
288 for (WayPoint w : waypoints) {
289 if (firstTime < 0.0) {
290 firstTime = w.time;
291 }
292 double offset = w.time - firstTime;
293 AudioMarker am = new AudioMarker(w.getCoor(), w, url, ml, w.time, offset);
294 // timeFromAudio intended for future use to shift markers of this type on synchronization
295 if (w == wayPointFromTimeStamp) {
296 am.timeFromAudio = true;
297 }
298 ml.data.add(am);
299 }
300
301 if (timedMarkersOmitted && !markers.timedMarkersOmitted) {
302 JOptionPane.showMessageDialog(Main.parent,
303 tr("Some waypoints with timestamps from before the start of the track or after the end were omitted or moved to the start."));
304 markers.timedMarkersOmitted = timedMarkersOmitted;
305 }
306 if (untimedMarkersOmitted && !markers.untimedMarkersOmitted) {
307 JOptionPane.showMessageDialog(Main.parent,
308 tr("Some waypoints which were too far from the track to sensibly estimate their time were omitted."));
309 markers.untimedMarkersOmitted = untimedMarkersOmitted;
310 }
311 }
312}
Note: See TracBrowser for help on using the repository browser.