This means that one can edit the movies shot during the vacations without worsening the already (relatively) low quality.
Specifically, it takes one or more files with the timings (start/end) and merges them into a destination file.
It's not usable by a non-programmer, but actually it's very easy to create a gui for it; unfortunately java doesn't help deadly simple applications deploying.
/*
* License: you can do what the heck you want with this file, as long as you
* reference me as starting author.
*
* @author Saverio Miroddi (com.inbox@pub.saverio - reverse the tokens to get
* the email)
*/
import quicktime.QTException;
import quicktime.QTSession;
import quicktime.io.IOConstants;
import quicktime.io.OpenMovieFile;
import quicktime.io.QTFile;
import quicktime.std.StdQTConstants;
import quicktime.std.movies.Movie;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
class SourceData
{
public final String fileName;
public final float start;
/** -1 = end */
public final float end;
public SourceData(String fileName, float start, float end)
{
this.fileName = fileName;
this.start = start;
this.end = end;
}
}
/**
* Edits (cuts/pastes) pieces of quicktime movies into a destination quicktime
* file.
* The advantage of using this method is that it's lossless - very useful when
* editing movies shot with a portable camera, which are already (relatively)
* low quality.
*
* Requires quicktime wrapper (QTJava.zip), that is installed along with
* QuickTime, in the lib/ext folder of the JRE); at the time of writing, the QT
* version is 7.5
*
* Note: I never tried executing it from the commandline, I always edited the
* params directly in the code, but it should work fine.
*/
public class QuickTimeEditing
{
private static final int SOURCE_TIMESCALE = 30;
private static final String REF_FILENAME = "qt_edit.ref.tmp";
public static void main(String[] args) throws Exception
{
args = new String[] {
"d:/tmp/male_singer_falsetto.mov",
"d:/desktop/videos/heli0.mov", "0", "-1",
"d:/desktop/videos/heli1.mov", "0", "-1",
"d:/desktop/videos/heli8.mov", "0", "-1",
"d:/desktop/videos/heli9.mov", "0", "-1",
"d:/desktop/videos/heli10.mov", "0", "-1",
};
String destFile = args[0];
SourceData[] sourceData = extractSourceData(args);
new QuickTimeEditing().edit(destFile, sourceData);
}
/**
* @param args first is skipped
*/
private static SourceData[] extractSourceData(String... args)
{
ListsourceData = new ArrayList ();
for (int i = 1; i < args.length; )
{
String filename = args[i++];
float start = Float.parseFloat(args[i++]);
float end = Float.parseFloat(args[i++]);
sourceData.add(new SourceData(filename, start, end));
}
return sourceData.toArray(new SourceData[0]);
}
// PUBLIC INSTANCE METHODS /////////////////////////////////////////////////
public void edit(String destFile, SourceData... sourcesData) throws Exception
{
String refFile = createRefFilename(destFile);
try {
System.out.println("Opening...");
QTSession.open();
joinSourceMovies(destFile, refFile, sourcesData);
}
finally {
System.out.println("Closing...");
QTSession.close();
System.out.println("Cleaning up...");
cleanupRefFiles(refFile);
}
}
// PRIVATE INSTANCE METHODS /////////////////////////////////////////////////
private final String createRefFilename(String destFile) throws IOException
{
File destFileDir = new File(destFile).getParentFile();
return new File(destFileDir, REF_FILENAME).getCanonicalPath();
}
private void joinSourceMovies(String destFile, String refFile, SourceData... sourcesData) throws QTException, IOException
{
Movie refMovie = Movie.createMovieFile(new QTFile(refFile),
StdQTConstants.kMoviePlayer, StdQTConstants.createMovieFileDeleteCurFile);
refMovie.setTimeScale(SOURCE_TIMESCALE);
int lastTimeScaled = 0;
for (SourceData sourceData : sourcesData)
{
System.out.println("Processing '" + sourceData.fileName + "' (" + sourceData.start + "->" + sourceData.end + ")...");
Movie sourceMovie = Movie.fromFile(OpenMovieFile.asRead(new QTFile(sourceData.fileName)));
int timeScale = sourceMovie.getTimeScale();
if (timeScale != SOURCE_TIMESCALE) throw new RuntimeException("Too lazy to process time scales != " + SOURCE_TIMESCALE + ": " + timeScale);
int startTimeScaled = (int)(sourceData.start * timeScale);
int lengthScaled = (int)(sourceData.end > 0 ?
sourceData.end * timeScale :
sourceMovie.getDuration() - startTimeScaled);
sourceMovie.insertSegment(refMovie, startTimeScaled, lengthScaled, lastTimeScaled);
lastTimeScaled += lengthScaled;
}
refMovie.flatten (0, // movieFlattenFlags
new QTFile(destFile),
StdQTConstants.kMoviePlayer, // creator
IOConstants.smSystemScript, // scriptTag
StdQTConstants.createMovieFileDeleteCurFile, // createQTFileFlags
StdQTConstants.movieInDataForkResID, // resId
destFile);
}
private void cleanupRefFiles(String refFile)
{
new File(refFile).delete();
new File(refFile + ".#res").delete();
}
}
No comments:
Post a Comment