364 lines
11 KiB
Java
364 lines
11 KiB
Java
package zall;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Queue;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
import javax.imageio.ImageIO;
|
|
import javax.servlet.ServletConfig;
|
|
import javax.servlet.http.HttpServlet;
|
|
|
|
import com.xuggle.mediatool.IMediaReader;
|
|
import com.xuggle.mediatool.IMediaWriter;
|
|
import com.xuggle.mediatool.MediaToolAdapter;
|
|
import com.xuggle.mediatool.ToolFactory;
|
|
import com.xuggle.mediatool.event.AudioSamplesEvent;
|
|
import com.xuggle.mediatool.event.IAddStreamEvent;
|
|
import com.xuggle.mediatool.event.IAudioSamplesEvent;
|
|
import com.xuggle.mediatool.event.IVideoPictureEvent;
|
|
import com.xuggle.mediatool.event.VideoPictureEvent;
|
|
import com.xuggle.xuggler.IAudioResampler;
|
|
import com.xuggle.xuggler.IAudioSamples;
|
|
import com.xuggle.xuggler.ICodec;
|
|
import com.xuggle.xuggler.IStreamCoder;
|
|
import com.xuggle.xuggler.IVideoPicture;
|
|
import com.xuggle.xuggler.IVideoResampler;
|
|
import com.xuggle.xuggler.video.ConverterFactory;
|
|
import com.xuggle.xuggler.video.IConverter;
|
|
|
|
import zall.bean.Media.Size;
|
|
import zall.bean.Video;
|
|
import zutil.StringUtil;
|
|
import zutil.db.DBConnection;
|
|
import zutil.log.LogUtil;
|
|
|
|
|
|
public class ZalleryTranscoder extends HttpServlet{
|
|
private static final Logger logger = LogUtil.getLogger();
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
// Media Queue
|
|
private static Queue<Video> transcodingQueue;
|
|
private static TranscoderThread worker;
|
|
|
|
public void init( ServletConfig config ){
|
|
try{
|
|
transcodingQueue = new LinkedList<Video>();
|
|
worker = new TranscoderThread();
|
|
worker.start();
|
|
|
|
// get untranscoded videos
|
|
DBConnection db = null;
|
|
try {
|
|
db = Zallery.getDB();
|
|
List<Video> incomplete = Video.loadUntransoded( db );
|
|
synchronized (transcodingQueue) {
|
|
transcodingQueue.addAll( incomplete );
|
|
transcodingQueue.notify();
|
|
}
|
|
} catch (Exception e) {
|
|
logger.log(Level.SEVERE, null, e);
|
|
}finally{
|
|
if( db != null ) db.close();
|
|
}
|
|
}
|
|
catch(Exception e){
|
|
logger.log(Level.SEVERE, "Unable to initialize ZalleryTranscoder!", e);
|
|
}
|
|
}
|
|
|
|
public void destroy( ){
|
|
worker.abort();
|
|
}
|
|
|
|
|
|
public static Video getProcessingVideo(){
|
|
return worker.currentVideo;
|
|
}
|
|
|
|
public static Queue<Video> getQueue(){
|
|
return transcodingQueue;
|
|
}
|
|
|
|
|
|
/**
|
|
* Add a video to the transcoding queue
|
|
*
|
|
* @param video is the video to transcode
|
|
*/
|
|
public static void addVideo(Video video) {
|
|
if( transcodingQueue == null ){
|
|
logger.severe("ZalleryTranscoder not initialized!");
|
|
return;
|
|
}
|
|
if( !transcodingQueue.contains(video) && worker.currentVideo != video ){
|
|
synchronized (transcodingQueue){
|
|
transcodingQueue.add( video );
|
|
transcodingQueue.notify();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Is the class that is doing the actual work of transcoding videos
|
|
*
|
|
* @author Ziver
|
|
*/
|
|
private static class TranscoderThread extends Thread{
|
|
private boolean stop;
|
|
protected long startTime;
|
|
protected Video currentVideo;
|
|
|
|
|
|
public void run(){
|
|
logger.info("ZalleryTranscoder thread started.");
|
|
try {
|
|
while( true ){
|
|
// Get video to transcode
|
|
while( !transcodingQueue.isEmpty() ){
|
|
currentVideo = transcodingQueue.poll();
|
|
startTime = System.currentTimeMillis();
|
|
logger.info("Starting transcoding video(id:"+currentVideo.getId()+")");
|
|
|
|
File originalFile = currentVideo.getFile( Size.ORIGINAL );
|
|
File mediumFile = currentVideo.getFile( Size.MEDIUM );
|
|
File smallFile = currentVideo.getFile( Size.SMALL );
|
|
|
|
///////////// Start Transcoding
|
|
// create a media reader
|
|
IMediaReader reader = ToolFactory.makeReader( originalFile.getPath() );
|
|
reader.addListener(new FrameGrabListener(reader, smallFile, 0.2));
|
|
reader.addListener(new ProgressListener(reader));
|
|
|
|
VideoTranscoderListener converter = new VideoTranscoderListener(640, 360);
|
|
reader.addListener(converter);
|
|
|
|
// create a media writer
|
|
IMediaWriter writer = ToolFactory.makeWriter(mediumFile.getPath(), reader);
|
|
converter.addListener(writer);
|
|
|
|
// create a media viewer with stats enabled for debugging
|
|
// add a viewer to the reader, to see the decoded media
|
|
//IMediaViewer mediaViewer = ToolFactory.makeViewer(true);
|
|
//reader.addListener(mediaViewer);
|
|
//writer.addListener(mediaViewer);
|
|
|
|
// read and decode packets from the source file and
|
|
// and dispatch decoded audio and video to the writer
|
|
while (reader.readPacket() == null);
|
|
|
|
// Incomplete transcoding
|
|
if( reader.isOpen() ){
|
|
logger.severe("Transcoding incomplete, removing incomplete files!");
|
|
reader.close();
|
|
mediumFile.delete();
|
|
smallFile.delete();
|
|
if( stop )
|
|
return;
|
|
}
|
|
else{
|
|
logger.info("Done transcoding video(id:"+currentVideo.getId()+") time: "+StringUtil.formatTimeToString(System.currentTimeMillis()-startTime));
|
|
currentVideo.setTranscoded(true);
|
|
try{
|
|
DBConnection db = Zallery.getDB();
|
|
currentVideo.save(db);
|
|
db.close();
|
|
}catch(Exception e){
|
|
logger.log(Level.SEVERE, "Unable to save video bean!", e);
|
|
}
|
|
}
|
|
currentVideo = null;
|
|
}
|
|
// Wait for new video to transcode
|
|
synchronized (transcodingQueue) {
|
|
transcodingQueue.wait();
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
logger.log(Level.SEVERE, "Transcoding thread has crashed!", e);
|
|
}
|
|
}
|
|
|
|
public void abort(){
|
|
stop = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class MyVideoListener extends MediaToolAdapter {
|
|
private Integer width;
|
|
private Integer height;
|
|
|
|
public MyVideoListener(Integer aWidth, Integer aHeight) {
|
|
this.width = aWidth;
|
|
this.height = aHeight;
|
|
}
|
|
|
|
@Override
|
|
public void onAddStream(IAddStreamEvent event) {
|
|
int streamIndex = event.getStreamIndex();
|
|
IStreamCoder streamCoder = event.getSource().getContainer().getStream(streamIndex).getStreamCoder();
|
|
if (streamCoder.getCodecType() == ICodec.Type.CODEC_TYPE_AUDIO) {
|
|
} else if (streamCoder.getCodecType() == ICodec.Type.CODEC_TYPE_VIDEO) {
|
|
streamCoder.setWidth(width);
|
|
streamCoder.setHeight(height);
|
|
}
|
|
super.onAddStream(event);
|
|
}
|
|
|
|
}
|
|
|
|
class VideoTranscoderListener extends MediaToolAdapter{
|
|
private int width;
|
|
private int height;
|
|
|
|
public VideoTranscoderListener(int width, int height) {
|
|
this.width = width;
|
|
this.height = height;
|
|
}
|
|
|
|
private IVideoResampler videoResampler = null;
|
|
private IAudioResampler audioResampler = null;
|
|
|
|
@Override
|
|
public void onAddStream(IAddStreamEvent event) {
|
|
int streamIndex = event.getStreamIndex();
|
|
IStreamCoder streamCoder = event.getSource().getContainer().getStream(streamIndex).getStreamCoder();
|
|
if (streamCoder.getCodecType() == ICodec.Type.CODEC_TYPE_AUDIO) {
|
|
streamCoder.setSampleRate( 44100 );
|
|
//streamCoder.setCodec( ICodec.ID.CODEC_ID_AAC );
|
|
//streamCoder.setBitRate( 128 );
|
|
}
|
|
else if (streamCoder.getCodecType() == ICodec.Type.CODEC_TYPE_VIDEO) {
|
|
streamCoder.setWidth( width );
|
|
streamCoder.setHeight( height );
|
|
|
|
//streamCoder.setCodec(ICodec.findEncodingCodec( ICodec.ID.CODEC_ID_H264 ));
|
|
//streamCoder.setBitRate( 800000 );
|
|
/*
|
|
int retval = Configuration.configure("/usr/local/xuggler/share/ffmpeg/libx264-superfast.ffpreset", streamCoder);
|
|
if (retval<0)
|
|
throw new RuntimeException("cound not cofigure coder from preset file");
|
|
*/
|
|
}
|
|
super.onAddStream(event);
|
|
}
|
|
|
|
@Override
|
|
public void onVideoPicture(IVideoPictureEvent event) {
|
|
IVideoPicture pic = event.getPicture();
|
|
if (videoResampler == null) {
|
|
videoResampler = IVideoResampler.make(width, height, pic.getPixelType(), pic.getWidth(), pic.getHeight(), pic.getPixelType());
|
|
}
|
|
IVideoPicture out = IVideoPicture.make(pic.getPixelType(), width, height);
|
|
videoResampler.resample(out, pic);
|
|
|
|
IVideoPictureEvent asc = new VideoPictureEvent(event.getSource(), out, event.getStreamIndex());
|
|
super.onVideoPicture(asc);
|
|
out.delete();
|
|
}
|
|
|
|
@Override
|
|
public void onAudioSamples(IAudioSamplesEvent event) {
|
|
IAudioSamples samples = event.getAudioSamples();
|
|
if (audioResampler == null) {
|
|
audioResampler = IAudioResampler.make(2, samples.getChannels(), 44100, samples.getSampleRate());
|
|
}
|
|
if (event.getAudioSamples().getNumSamples() > 0) {
|
|
IAudioSamples out = IAudioSamples.make(samples.getNumSamples(), samples.getChannels());
|
|
audioResampler.resample(out, samples, samples.getNumSamples());
|
|
|
|
AudioSamplesEvent asc = new AudioSamplesEvent(event.getSource(), out, event.getStreamIndex());
|
|
super.onAudioSamples(asc);
|
|
out.delete();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
class ProgressListener extends MediaToolAdapter {
|
|
private long currentLength;
|
|
private long totalLength;
|
|
private int progress = -1;
|
|
|
|
public ProgressListener(IMediaReader reader) {
|
|
if( !reader.isOpen() )
|
|
reader.open(); // read container
|
|
this.totalLength = reader.getContainer().getDuration();
|
|
}
|
|
|
|
@Override
|
|
public void onVideoPicture(IVideoPictureEvent event) {
|
|
currentLength = event.getTimeStamp();
|
|
|
|
if( (int)(100*getProgress()) != progress ){
|
|
progress = (int)(100*getProgress());
|
|
System.out.print("\n"+(int)(100*getProgress())+"% ");
|
|
}
|
|
else System.out.print(".");
|
|
}
|
|
|
|
public long getProcessedLength(){
|
|
return currentLength;
|
|
}
|
|
|
|
public long getTotalLength(){
|
|
return totalLength;
|
|
}
|
|
|
|
public double getProgress(){
|
|
if(totalLength > 0)
|
|
return ((double)currentLength/totalLength);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
class FrameGrabListener extends MediaToolAdapter {
|
|
private long totalLength;
|
|
private long grabAt;
|
|
private File outputFile;
|
|
|
|
public FrameGrabListener(IMediaReader reader, File outputFile, double at) {
|
|
if( !reader.isOpen() )
|
|
reader.open(); // read container
|
|
this.totalLength = reader.getContainer().getDuration();
|
|
this.outputFile = outputFile;
|
|
setAtProgress(at);
|
|
}
|
|
|
|
@Override
|
|
public void onVideoPicture(IVideoPictureEvent event) {
|
|
try{
|
|
long currentLength = event.getTimeStamp();
|
|
|
|
if( grabAt > 0 && currentLength > grabAt ){
|
|
System.out.println("\nScreanshoot!!!");
|
|
|
|
ConverterFactory.Type mConverterType = ConverterFactory.findRegisteredConverter(
|
|
ConverterFactory.XUGGLER_BGR_24);
|
|
IConverter mVideoConverter = ConverterFactory.createConverter(
|
|
mConverterType.getDescriptor(),
|
|
event.getPicture());
|
|
|
|
ImageIO.write(
|
|
mVideoConverter.toImage(event.getPicture()),
|
|
"png",
|
|
outputFile);
|
|
grabAt = -1;
|
|
}
|
|
}catch(IOException e){
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
public void setAtProgress(double procent){
|
|
grabAt = (long)(totalLength * procent);
|
|
}
|
|
}
|
|
|
|
|