/*
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not
* distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2014, OpenSpace Solutions LLC. All Right Reserved.
*/
package com.chiorichan.dvr;
import com.chiorichan.ChatColor;
import com.chiorichan.Loader;
import com.chiorichan.dvr.registry.VideoInput;
import com.chiorichan.dvr.storage.Interface;
import com.google.common.collect.Maps;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import static java.lang.Thread.sleep;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;
import org.joda.time.DateTime;
/**
*
* @author Chiori Greene
*/
public class VideoWriter implements Runnable
{
public boolean isRunning;
public Thread currentThread;
private Interface storageInterface = new Interface();
private long lastTen = 0L;
private ZipOutputStream containerStream = null;
private boolean frameEncoding = false;
private final TreeMap<DateTime, BufferedImage> timeCodedFrames = Maps.newTreeMap();
public boolean detachedFromInput = false;
private final VideoInput vi;
private File destFile;
private ZipParameters parameters;
public VideoWriter( VideoInput _vi )
{
vi = _vi;
DVRLoader.getExecutor().execute( this );
parameters = new ZipParameters();
parameters.setCompressionMethod( Zip4jConstants.COMP_DEFLATE );
parameters.setCompressionLevel( Zip4jConstants.DEFLATE_LEVEL_NORMAL );
parameters.setEncryptFiles( true );
parameters.setEncryptionMethod( Zip4jConstants.ENC_METHOD_AES );
parameters.setAesKeyStrength( Zip4jConstants.AES_STRENGTH_256 );
parameters.setPassword( "OpenSpaceDVR2014" ); // Generate a private key for each DVR install. Installation ID maybe?
}
@Override
public void run()
{
currentThread = Thread.currentThread();
Loader.getLogger().info( "Starting Video Writer Thread - " + currentThread.getName() );
isRunning = true;
do
{
try
{
if ( containerStream == null )
changeDestFile();
Entry<DateTime, BufferedImage> firstFrame = getFrame();
if ( firstFrame != null )
{
frameHandler( firstFrame.getKey(), firstFrame.getValue() );
timeCodedFrames.remove( firstFrame.getKey() );
}
Thread.sleep( 10L );
}
catch ( Exception ex )
{
Loader.getLogger().severe( "Exception Encountered in the Video Writer Thread (" + currentThread.getName() + ")", ex );
}
}
while ( DVRLoader.isRunning && !detachedFromInput );
Loader.getLogger().info( "Stopping Video Writer Thread - " + Thread.currentThread().getName() );
try
{
finishUp();
}
catch ( IOException e )
{
e.printStackTrace();
}
isRunning = false;
currentThread = null;
Loader.getLogger().info( "Video Writer Thread STOPPED! - " + Thread.currentThread().getName() );
}
public synchronized void addFrame( DateTime dt, BufferedImage bi )
{
timeCodedFrames.put( dt, bi );
}
private synchronized Entry<DateTime, BufferedImage> getFrame()
{
if ( timeCodedFrames.isEmpty() )
return null;
return timeCodedFrames.firstEntry();
}
private void changeDestFile() throws IOException
{
File lastDestFile = destFile;
destFile = storageInterface.calculateContainingFile( new DateTime(), vi.getChannelName() );
Loader.getLogger().info( "New destination selected for input " + vi.getChannelName() + ": " + destFile.getAbsolutePath() );
// Close previous zip stream
if ( containerStream != null )
containerStream.finish();
// Open new zip stream
containerStream = new ZipOutputStream( new FileOutputStream( destFile ) );
containerStream.setComment( "OpenSpaceDVR video storage container!" );
}
private void frameHandler( DateTime dt, BufferedImage img )
{
try
{
if ( img == null )
return;
frameEncoding = true;
long start = System.currentTimeMillis();
if ( lastTen != storageInterface.getTen( dt ) )
{
lastTen = storageInterface.getTen( dt );
changeDestFile();
}
containerStream.putNextEntry( new ZipEntry( dt.getMillis() + ".jpg" ) );
ByteArrayOutputStream bs = new ByteArrayOutputStream();
ImageIO.write( img, "JPG", bs );
containerStream.write( bs.toByteArray() );
containerStream.closeEntry();
Loader.getLogger().info( ChatColor.YELLOW + "Writing Frame: Capture Time: " + dt.toString() + ", File Size: " + bs.size() + " bytes, Frames Buffered: " + timeCodedFrames.size() + ", Time Taken: " + (System.currentTimeMillis() - start) + ", Thread: " + Thread.currentThread().getName() );
frameEncoding = false;
}
catch ( IOException ex )
{
Loader.getLogger().severe( "Exception encountered within the frameHandler method:", ex );
}
}
private synchronized void finishUp() throws IOException
{
while ( frameEncoding )
{
try
{
sleep( 10 );
}
catch ( InterruptedException ex )
{
}
}
containerStream.close();
}
}