mirror of
https://gitlab.com/AutumnMeowMeow/jexer
synced 2024-09-19 03:40:20 -06:00
189 lines
7 KiB
Java
189 lines
7 KiB
Java
import java.awt.image.BufferedImage;
|
|
import java.io.File;
|
|
import jexer.TApplication;
|
|
import jexer.TExceptionDialog;
|
|
import jexer.TImage;
|
|
import jexer.TWindow;
|
|
import jexer.event.TResizeEvent;
|
|
import org.bytedeco.javacv.FFmpegFrameGrabber;
|
|
import org.bytedeco.javacv.Java2DFrameConverter;
|
|
import static org.bytedeco.ffmpeg.global.avutil.av_log_set_level;
|
|
import static org.bytedeco.ffmpeg.global.avutil.AV_LOG_QUIET;
|
|
|
|
/**
|
|
* XtermVideoPlayer plays videos in an Xterm. It uses the JavaCV library to
|
|
* obtain images from a video file and puts them into a TImage. The initial
|
|
* few seconds will often stutter as the Java JIT compiler steps in.
|
|
*
|
|
* See JavaCV at https://github.com/bytedeco/javacv .
|
|
*
|
|
* Compile it with:
|
|
* javac -cp javacv.jar:ffmpeg.jar:jexer.jar XtermVideoPlayer.java
|
|
*
|
|
* Run it with (assuming platform is linux-x86_64):
|
|
* java -cp javacv.jar:ffmpeg.jar:ffmpeg-linux-x86_64.jar:jexer.jar:. XtermVideoPlayer filename
|
|
*/
|
|
public class XtermVideoPlayer extends TApplication {
|
|
|
|
// The image reference.
|
|
private TImage image;
|
|
|
|
// Constructor sets up widgets.
|
|
public XtermVideoPlayer(String filename) throws Exception {
|
|
super(BackendType.XTERM);
|
|
|
|
// Create standard menus for Tool, File, and Window.
|
|
addToolMenu();
|
|
addFileMenu();
|
|
addWindowMenu();
|
|
|
|
// Make it cute.
|
|
getTheme().setFemme();
|
|
System.setProperty("jexer.TWindow.borderStyleForeground", "round");
|
|
System.setProperty("jexer.TWindow.borderStyleModal", "round");
|
|
System.setProperty("jexer.TWindow.borderStyleMoving", "round");
|
|
System.setProperty("jexer.TWindow.borderStyleInactive", "round");
|
|
setDesktop(null);
|
|
setHideStatusBar(true);
|
|
|
|
// Create a window for the image. The resize event is overridden so
|
|
// that the internal image field changes in size with the window.
|
|
TWindow window = new TWindow(this, filename, 0, 0,
|
|
getScreen().getWidth() / 2, getScreen().getHeight() / 2) {
|
|
|
|
@Override
|
|
public void onResize(final TResizeEvent event) {
|
|
if (event.getType() == TResizeEvent.Type.WIDGET) {
|
|
TResizeEvent imageSize;
|
|
imageSize = new TResizeEvent(event.getBackend(),
|
|
TResizeEvent.Type.WIDGET, event.getWidth() - 2,
|
|
event.getHeight() - 2);
|
|
image.onResize(imageSize);
|
|
}
|
|
}
|
|
};
|
|
window.setAlpha(255);
|
|
|
|
// Add the image field. We will load frames into this.
|
|
image = window.addImage(0, 0, window.getWidth() - 2,
|
|
window.getHeight() - 2, new BufferedImage(720, 360,
|
|
BufferedImage.TYPE_INT_ARGB), 0, 0);
|
|
// Be able to see the movie no matter what size the image/window is.
|
|
image.setScaleType(TImage.Scale.SCALE);
|
|
}
|
|
|
|
// Function that grabs frames from a file and displays them in image.
|
|
private void getFrames(final File file) {
|
|
FFmpegFrameGrabber grabber = null;
|
|
try {
|
|
// Make ffmpeg quiet. If we don't do this there will be stuff
|
|
// emitted to stderr.
|
|
av_log_set_level(AV_LOG_QUIET);
|
|
|
|
// Start the video decoder.
|
|
Java2DFrameConverter conv = new Java2DFrameConverter();
|
|
grabber = new FFmpegFrameGrabber(file);
|
|
grabber.start();
|
|
long lastFrameMillis = 0;
|
|
|
|
// Ideally we would sync with the real video speed and drop
|
|
// frames as needed. For this example we will grab at the
|
|
// reported fps, and (due to how TApplication runs all of its
|
|
// invokeLaters at once) catch up to dropped frames.
|
|
double frameRate = grabber.getVideoFrameRate();
|
|
int fps = (int) frameRate;
|
|
final long FRAME_TIME = 1000 / fps;
|
|
|
|
// Get a rough frame count.
|
|
int totalFrames = grabber.getLengthInFrames();
|
|
int frameCount = 0;
|
|
|
|
// Keep adding images as long as the application is running.
|
|
while (isRunning()) {
|
|
BufferedImage frame;
|
|
frame = conv.convert(grabber.grabImage());
|
|
long now = System.currentTimeMillis();
|
|
if ((image != null) && (frame != null)) {
|
|
frameCount++;
|
|
if (now - lastFrameMillis > FRAME_TIME) {
|
|
invokeLater(new Runnable() {
|
|
public void run() {
|
|
image.setImage(frame);
|
|
doRepaint();
|
|
}
|
|
});
|
|
lastFrameMillis = now;
|
|
}
|
|
} else if (((image != null) && (frame == null)
|
|
&& (frameCount > 0))
|
|
|| (frameCount >= totalFrames)
|
|
) {
|
|
// We are out of frames, the movie is over.
|
|
image.getWindow().setTitle(image.getWindow().getTitle() +
|
|
" (Completed)");
|
|
doRepaint();
|
|
break;
|
|
}
|
|
|
|
// Pause until is time for the next frame.
|
|
long sleepMillis = FRAME_TIME - (now - lastFrameMillis);
|
|
if ((sleepMillis > 0) && (sleepMillis <= (int) (1000 / fps))) {
|
|
try {
|
|
Thread.sleep(sleepMillis);
|
|
} catch (InterruptedException e) {
|
|
// SQUASH
|
|
}
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
// Show this exception to the user.
|
|
invokeLater(new Runnable() {
|
|
public void run() {
|
|
new TExceptionDialog(XtermVideoPlayer.this, e);
|
|
}
|
|
});
|
|
// And exit the thread ...
|
|
}
|
|
if (grabber != null) {
|
|
// Try to shut down FFmpeg.
|
|
try {
|
|
grabber.release();
|
|
} catch (Exception e) {
|
|
// Show this exception to the user.
|
|
invokeLater(new Runnable() {
|
|
public void run() {
|
|
new TExceptionDialog(XtermVideoPlayer.this, e);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show FPS.
|
|
*/
|
|
@Override
|
|
protected void onPreDraw() {
|
|
menuTrayText = String.format("FPS %d", getFramesPerSecond());
|
|
}
|
|
|
|
// Main entry point.
|
|
public static void main(String [] args) throws Exception {
|
|
if (args.length == 0) {
|
|
System.err.println("USAGE: java XtermVideoPlayer filename");
|
|
System.exit(-1);
|
|
}
|
|
File file = new File(args[0]);
|
|
if (!file.canRead()) {
|
|
System.err.println("Cannot read from file: " + args[0]);
|
|
System.exit(-1);
|
|
}
|
|
|
|
XtermVideoPlayer app = new XtermVideoPlayer(file.getName());
|
|
// The application will spin on its thread.
|
|
(new Thread(app)).start();
|
|
|
|
// Frames will continue to grab on the main thread.
|
|
app.getFrames(new File(args[0]));
|
|
}
|
|
}
|