// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.videomapping;
import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.swing.InputVerifier;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.SwingUtilities;
import javax.swing.text.MaskFormatter;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.actions.JosmAction;
import org.openstreetmap.josm.gui.layer.GpxLayer;
import org.openstreetmap.josm.gui.layer.Layer;
import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
import org.openstreetmap.josm.plugins.Plugin;
import org.openstreetmap.josm.plugins.PluginInformation;
import org.openstreetmap.josm.plugins.videomapping.video.GPSVideoPlayer;
import org.openstreetmap.josm.plugins.videomapping.video.VideoEngine;
import org.openstreetmap.josm.tools.Shortcut;
import uk.co.caprica.vlcj.player.DeinterlaceMode;
/**
* This Plugin allows you to link multiple videos against a GPS track and playback both synchronously
* @author Matthias Meißer (digi_c at arcor dot de)
*/
public class VideoPlugin extends Plugin implements LayerChangeListener, ActiveLayerChangeListener {
private JMenu VMenu, VDeinterlacer;
private JosmAction VAdd, /*VRemove,*/ VStart, Vbackward, Vforward, VJump, Vfaster, Vslower, Vloop;
private JRadioButtonMenuItem VIntBob, VIntNone, VIntLinear;
private JCheckBoxMenuItem VCenterIcon, VSubTitles;
private JMenuItem VJumpLength, VLoopLength;
private final String PROP_MRU = "videomapping.mru";
private final String PROP_AUTOCENTER = "videomapping.autocenter";
private final String PROP_JUMPLENGTH = "videomapping.jumplength";
private final String PROP_LOOPLENGTH = "videomapping.looplength";
// private String deinterlacer;
private boolean autoCenter;
private Integer jumpLength, loopLength;
private String mostRecentFolder;
private GpxLayer gpsLayer;
private VideoPositionLayer videoPositionLayer;
private GPSVideoPlayer gpsVideoPlayer;
public static String VLC_VERSION = "2.1.x";
public VideoPlugin(PluginInformation info) {
super(info);
VideoEngine.setupPlayer();
Main.getLayerManager().addLayerChangeListener(this);
Main.getLayerManager().addActiveLayerChangeListener(this);
createMenusAndShortCuts();
enableVideoControlMenus(false);
setDefaults();
loadProperties();
}
private void createMenusAndShortCuts() {
VMenu = Main.main.menu.addMenu("Video", tr("Video"), KeyEvent.VK_Q, Main.main.menu.getDefaultMenuPos(), ht("/Plugin/Videomapping"));
VMenu.setEnabled(false);
VAdd = new JosmAction(tr("Import Video"), "videomapping", tr("Sync a video against this GPS track"), null, false) {
@Override public void actionPerformed(ActionEvent arg0) {
importVideoFile();
}
};
/*VRemove=*/ new JosmAction(tr("Remove Video"), "videomapping", tr("removes current video from layer"), null, false) {
@Override public void actionPerformed(ActionEvent arg0) {
}
};
// CHECKSTYLE.OFF: LineLength
VStart = new JosmAction(tr("Play/Pause"), "audio-playpause", tr("starts/pauses video playback"),
Shortcut.registerShortcut("videomapping:startstop", tr("Video: {0}", tr("Play/Pause")), KeyEvent.VK_NUMPAD5, Shortcut.DIRECT), false, "vm_play_pause", false) {
@Override public void actionPerformed(ActionEvent e) {
if (gpsVideoPlayer != null) {
gpsVideoPlayer.pause();
}
}
};
Vbackward = new JosmAction(tr("Backward"), "audio-prev", tr("jumps n sec back"),
Shortcut.registerShortcut("videomapping:backward", tr("Video: {0}", tr("Backward")), KeyEvent.VK_NUMPAD4, Shortcut.DIRECT), false, "vm_prev", false) {
@Override public void actionPerformed(ActionEvent e) {
if (gpsVideoPlayer != null) {
gpsVideoPlayer.backward();
}
}
};
Vforward = new JosmAction(tr("Forward"), "audio-next", tr("jumps n sec forward"),
Shortcut.registerShortcut("videomapping:forward", tr("Video: {0}", tr("Forward")), KeyEvent.VK_NUMPAD6, Shortcut.DIRECT), false, "vm_next", false) {
@Override public void actionPerformed(ActionEvent e) {
if (gpsVideoPlayer != null) {
gpsVideoPlayer.forward();
}
}
};
Vfaster = new JosmAction(tr("Faster"), "audio-faster", tr("faster playback"),
Shortcut.registerShortcut("videomapping:faster", tr("Video: {0}", tr("Faster")), KeyEvent.VK_NUMPAD8, Shortcut.DIRECT), false, "vm_faster", false) {
@Override public void actionPerformed(ActionEvent e) {
if (gpsVideoPlayer != null) {
gpsVideoPlayer.setSpeed(gpsVideoPlayer.getSpeed()+20);
}
}
};
Vslower = new JosmAction(tr("Slower"), "audio-slower", tr("slower playback"),
Shortcut.registerShortcut("videomapping:slower", tr("Video: {0}", tr("Slower")), KeyEvent.VK_NUMPAD2, Shortcut.DIRECT), false, "vm_slower", false) {
@Override public void actionPerformed(ActionEvent e) {
if (gpsVideoPlayer != null) {
gpsVideoPlayer.setSpeed(gpsVideoPlayer.getSpeed()-20);
}
}
};
VJump = new JosmAction(tr("Jump To"), "jumpto", tr("jumps to the entered gps time"), null, false) {
@Override public void actionPerformed(ActionEvent e) {
showJumpTo();
}
};
Vloop = new JosmAction(tr("Loop"), "loop", tr("loops n sec around current position"),
Shortcut.registerShortcut("videomapping:loop", tr("Video: {0}", tr("Loop")), KeyEvent.VK_NUMPAD7, Shortcut.DIRECT), false) {
@Override public void actionPerformed(ActionEvent e) {
if (gpsVideoPlayer != null) {
gpsVideoPlayer.toggleLooping();
}
}
};
//now the options menu
VCenterIcon = new JCheckBoxMenuItem(new JosmAction(tr("Keep centered"), (String) null, tr("follows the video icon automatically"), null, false, "vm_keepcentered", false) {
@Override public void actionPerformed(ActionEvent e) {
if (videoPositionLayer != null) {
videoPositionLayer.setAutoCenter(VCenterIcon.isSelected());
}
}
});
VSubTitles = new JCheckBoxMenuItem(new JosmAction(tr("Subtitles"), (String) null, tr("Show subtitles in video"), null, false, "vm_subtitles", false) {
@Override public void actionPerformed(ActionEvent e) {
if (gpsVideoPlayer != null) {
gpsVideoPlayer.setSubtitles(VSubTitles.isSelected());
}
}
});
VJumpLength = new JMenuItem(new JosmAction(tr("Jump length"), (String) null, tr("Set the length of a jump"), null, false, "vm_jumplen", false) {
@Override public void actionPerformed(ActionEvent e) {
Object[] possibilities = {"200", "500", "1000", "2000", "10000"};
String s = (String) JOptionPane.showInputDialog(Main.parent, tr("Jump in video for x ms"), tr("Jump length"), JOptionPane.QUESTION_MESSAGE, null, possibilities, jumpLength);
jumpLength = Integer.getInteger(s);
saveProperties();
}
});
VLoopLength = new JMenuItem(new JosmAction(tr("Loop length"), (String) null, tr("Set the length around a looppoint"), null, false, "vm_looplen", false) {
@Override public void actionPerformed(ActionEvent e) {
Object[] possibilities = {"500", "1000", "3000", "5000", "10000"};
String s = (String) JOptionPane.showInputDialog(Main.parent, tr("Jump in video for x ms"), tr("Loop length"), JOptionPane.QUESTION_MESSAGE, null, possibilities, loopLength);
loopLength = Integer.getInteger(s);
saveProperties();
}
});
//TODO read deinterlacers list out of videoengine
VDeinterlacer = new JMenu("Deinterlacer");
VIntNone = new JRadioButtonMenuItem(new JosmAction(tr("none"), (String) null, tr("no deinterlacing"), null, false, "vm_deinterlacer", false) {
@Override public void actionPerformed(ActionEvent e) {
if (gpsVideoPlayer != null) {
gpsVideoPlayer.setDeinterlacer(null);
}
}
});
VIntBob = new JRadioButtonMenuItem(new JosmAction("bob", (String) null, tr("deinterlacing using line doubling"), null, false, "vm_bobdeinterlace", false) {
@Override public void actionPerformed(ActionEvent e) {
if (gpsVideoPlayer != null) {
gpsVideoPlayer.setDeinterlacer(DeinterlaceMode.BOB);
}
}
});
VIntLinear = new JRadioButtonMenuItem(new JosmAction("linear", (String) null, tr("deinterlacing using linear interpolation"), null, false, "vm_lineardeinterlace", false) {
@Override public void actionPerformed(ActionEvent e) {
if (gpsVideoPlayer != null) {
gpsVideoPlayer.setDeinterlacer(DeinterlaceMode.LINEAR);
}
}
});
// CHECKSTYLE.ON: LineLength
VDeinterlacer.add(VIntNone);
VDeinterlacer.add(VIntBob);
VDeinterlacer.add(VIntLinear);
VMenu.add(VAdd);
VMenu.add(VStart);
VMenu.add(Vbackward);
VMenu.add(Vforward);
VMenu.add(Vfaster);
VMenu.add(Vslower);
VMenu.add(Vloop);
VMenu.add(VJump);
VMenu.addSeparator();
VMenu.add(VCenterIcon);
VMenu.add(VJumpLength);
VMenu.add(VLoopLength);
VMenu.add(VDeinterlacer);
VMenu.add(VSubTitles);
}
protected void importVideoFile() {
JFileChooser fc = new JFileChooser(mostRecentFolder);
fc.setSelectedFile(new File(mostRecentFolder));
if (fc.showOpenDialog(Main.parent) != JFileChooser.CANCEL_OPTION) {
mostRecentFolder = fc.getSelectedFile().getAbsolutePath();
saveProperties();
if (videoPositionLayer == null && gpsLayer != null) {
videoPositionLayer = new VideoPositionLayer(gpsLayer);
gpsVideoPlayer = new GPSVideoPlayer(new SimpleDateFormat("hh:mm:ss"), videoPositionLayer);
gpsVideoPlayer.setJumpLength(jumpLength);
gpsVideoPlayer.setLoopLength(loopLength);
enableVideoControlMenus(true);
}
if (gpsVideoPlayer != null && gpsVideoPlayer.isCorrectlyInitiliazed()) {
gpsVideoPlayer.addVideo(fc.getSelectedFile());
} else {
JOptionPane.showMessageDialog(Main.parent,
tr("VLC library is not correctly initialized."+
" Please check that VLC {0} is correctly installed on your system."+
" Its architecture (32/64 bits) must also be the same as the JRE that runs JOSM.", VLC_VERSION),
tr("Error"), JOptionPane.ERROR_MESSAGE);
}
}
}
private void enableVideoControlMenus(boolean b) {
VStart.setEnabled(b);
Vbackward.setEnabled(b);
Vforward.setEnabled(b);
Vloop.setEnabled(b);
Vfaster.setEnabled(b);
Vslower.setEnabled(b);
VJump.setEnabled(b);
}
private void setDefaults() {
autoCenter = false;
// deinterlacer="";
jumpLength = 1000;
loopLength = 6000;
mostRecentFolder = System.getProperty("user.home");
}
private void loadProperties() {
String temp;
temp = Main.pref.get(PROP_AUTOCENTER);
if ((temp != null) && (temp.length() != 0))
autoCenter = Boolean.getBoolean(temp);
temp = Main.pref.get(PROP_JUMPLENGTH);
if ((temp != null) && (temp.length() != 0))
jumpLength = Integer.valueOf(temp);
temp = Main.pref.get(PROP_LOOPLENGTH);
if ((temp != null) && (temp.length() != 0))
loopLength = Integer.valueOf(temp);
temp = Main.pref.get(PROP_MRU);
if ((temp != null) && (temp.length() != 0))
mostRecentFolder = Main.pref.get(PROP_MRU);
}
private void saveProperties() {
Main.pref.put(PROP_AUTOCENTER, autoCenter);
Main.pref.put(PROP_JUMPLENGTH, jumpLength.toString());
Main.pref.put(PROP_LOOPLENGTH, loopLength.toString());
Main.pref.put(PROP_MRU, mostRecentFolder);
}
private void showJumpTo() {
try {
JOptionPane d = new JOptionPane(tr("Jump to"), JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION);
SimpleDateFormat gpsTimeFormat = new SimpleDateFormat("HH:mm:ss");
String timerange = gpsTimeFormat.format(videoPositionLayer.getFirstWayPoint().getTime())+" - ";
timerange = timerange+gpsTimeFormat.format(videoPositionLayer.getLastWayPoint().getTime());
d.add(new JLabel(timerange)); //TODO for some reason this doesn't work -> use dialog
final JFormattedTextField inp = new JFormattedTextField(new MaskFormatter("##:##:##"));
inp.setText(gpsTimeFormat.format(videoPositionLayer.getGPSDate()));
inp.setInputVerifier(new InputVerifier() {
@Override
public boolean verify(JComponent input) {
return false;
}
});
//hack to set the focus
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
inp.requestFocus();
inp.setCaretPosition(0);
}
});
if (JOptionPane.showConfirmDialog(Main.parent, inp, tr("Jump to GPS time"), JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) {
//add the day to the time
Date t = gpsTimeFormat.parse(inp.getText());
Calendar time = Calendar.getInstance();
Calendar date = Calendar.getInstance();
time.setTime(t);
date.setTime(videoPositionLayer.getFirstWayPoint().getTime());
time.set(date.get(Calendar.YEAR), date.get(Calendar.MONTH), date.get(Calendar.DATE));
if (t != null) {
videoPositionLayer.jump(time.getTime());
}
}
} catch (ParseException e1) {
e1.printStackTrace();
}
}
private void handleLayer(Layer l) {
VMenu.setEnabled(true);
if (l instanceof GpxLayer) {
VAdd.setEnabled(true);
gpsLayer = (GpxLayer) l;
//TODO append to GPS Layer menu
}
}
@Override
public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
handleLayer(Main.getLayerManager().getActiveLayer());
}
@Override
public void layerOrderChanged(LayerOrderChangeEvent e) {
// Do nothing
}
@Override
public void layerAdded(LayerAddEvent e) {
handleLayer(e.getAddedLayer());
}
@Override
public void layerRemoving(LayerRemoveEvent e) {
if (e.getRemovedLayer() instanceof VideoPositionLayer)
enableVideoControlMenus(false);
handleLayer(e.getRemovedLayer());
}
}