/*
* Created on Jun 7, 2004
*
* To change the template for this generated file go to
* Window - Preferences - Java - Code Generation - Code and Comments
*/
package org.kc7bfi.jflac.apps;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Panel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import org.kc7bfi.jflac.Constants;
import org.kc7bfi.jflac.FLACDecoder;
import org.kc7bfi.jflac.io.BitOutputStream;
import org.kc7bfi.jflac.metadata.Metadata;
import org.kc7bfi.jflac.metadata.SeekPoint;
import org.kc7bfi.jflac.metadata.SeekTable;
import org.kc7bfi.jflac.metadata.StreamInfo;
/**
* Assemble several FLAC files into one album.
*
* @author kc7bfi
*/
public class FlacPacker extends JFrame {
private JTextArea textArea = new JTextArea(16, 50);
private JButton addButton = new JButton("Add Files");
private JButton makeButton = new JButton("Pack FLAC");
private ArrayList flacFiles = new ArrayList();
private ArrayList albumFiles = new ArrayList();
private StreamInfo masterStreamInfo = null;
private byte[] buffer = new byte[64 * 1024];
/**
* Constructor.
*
* @param title Frame title
*/
public FlacPacker(String title) {
super(title);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.getContentPane().setLayout(new BorderLayout());
// text area
textArea.setText("");
textArea.setAutoscrolls(true);
this.getContentPane().add(textArea, BorderLayout.CENTER);
// button pannel
Panel buttonPanel = new Panel();
buttonPanel.setLayout(new FlowLayout());
buttonPanel.add(addButton);
buttonPanel.add(makeButton);
this.getContentPane().add(buttonPanel, BorderLayout.SOUTH);
this.pack();
addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
addFilesToList();
}
});
makeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
try {
packFlac();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
private void appendMsg(String msg) {
textArea.setText(textArea.getText() + msg + "\n");
textArea.repaint();
}
private void addFilesToList() {
JFileChooser chooser = new JFileChooser();
ExtensionFileFilter filter = new ExtensionFileFilter();
filter.addExtension("flac");
filter.setDescription("FLAC files");
chooser.setMultiSelectionEnabled(true);
chooser.setFileFilter(filter);
chooser.setCurrentDirectory(new File("."));
int returnVal = chooser.showOpenDialog(this);
if (returnVal != JFileChooser.APPROVE_OPTION) {
return;
}
File[] files = chooser.getSelectedFiles();
for (int i = 0; i < files.length; i++) {
flacFiles.add(files[i]);
}
}
private File getOutputFile() {
JFileChooser chooser = new JFileChooser();
ExtensionFileFilter filter = new ExtensionFileFilter();
filter.addExtension("flac");
filter.setDescription("FLAC files");
chooser.setFileFilter(filter);
chooser.setCurrentDirectory(new File("."));
int returnVal = chooser.showOpenDialog(this);
if (returnVal != JFileChooser.APPROVE_OPTION) {
return null;
}
File file = chooser.getSelectedFile();
return file;
}
private SeekTable makeSeekTable() {
long lastSampleNumber = 0;
long lastStreamOffset = 0;
// process selected files
for (int i = 0; i < flacFiles.size(); i++) {
File file = (File) flacFiles.get(i);
try {
FileInputStream is = new FileInputStream(file);
FLACDecoder decoder = new FLACDecoder(is);
decoder.readMetadata();
StreamInfo info = decoder.getStreamInfo();
if (masterStreamInfo == null) {
masterStreamInfo = info;
masterStreamInfo.setTotalSamples(0);
}
if (!info.compatiable(masterStreamInfo)) {
appendMsg("Bad StreamInfo " + file + ": " + info);
continue;
}
masterStreamInfo.addTotalSamples(info.getTotalSamples());
SeekPoint seekPoint = new SeekPoint(lastSampleNumber, lastStreamOffset, 0);
//decoder.processMetadata();
long frameStartOffs = decoder.getTotalBytesRead();
PackerFile aFile = new PackerFile(file, seekPoint, frameStartOffs);
albumFiles.add(aFile);
//System.out.println("Do decode " +i);
try {
decoder.decodeFrames();
}
catch (EOFException e) {
//appendMsg("File " + file + ": " + e);
}
//System.out.println("Done decode");
long frameEndOffs = decoder.getTotalBytesRead();
//appendMsg(frameStartOffs + " " + frameEndOffs + " " + decoder.getSamplesDecoded());
lastSampleNumber += decoder.getSamplesDecoded();
lastStreamOffset += frameEndOffs - frameStartOffs;
}
catch (FileNotFoundException e) {
appendMsg("File " + file + ": " + e);
}
catch (IOException e) {
appendMsg("File " + file + ": " + e);
}
}
// make Seek Table
SeekPoint[] points = new SeekPoint[albumFiles.size()];
SeekTable seekTable = new SeekTable(points, true);
int metadataHeader = ( Metadata.STREAM_METADATA_IS_LAST_LEN + Metadata.STREAM_METADATA_TYPE_LEN + Metadata.STREAM_METADATA_LENGTH_LEN ) / 8;
int metadataOffset = Constants.STREAM_SYNC_STRING.length + masterStreamInfo.calcLength() + seekTable.calcLength() + metadataHeader * 2;
for (int i = 0; i < albumFiles.size(); i++) {
PackerFile aFile = (PackerFile) albumFiles.get(i);
appendMsg("SeekTable build " + i + " Offset=" + aFile.seekPoint.getStreamOffset() + " header=" + metadataOffset);
aFile.seekPoint.setStreamOffset(aFile.seekPoint.getStreamOffset() + metadataOffset);
points[i] = aFile.seekPoint;
}
return seekTable;
}
private void packFlac() throws IOException {
// get output file
File outFile = getOutputFile();
if (outFile == null) {
return;
}
BitOutputStream os = null;
FileOutputStream fos;
try {
fos = new FileOutputStream(outFile);
os = new BitOutputStream(fos);
}
catch (FileNotFoundException e1) {
e1.printStackTrace();
return;
}
// get seek table
SeekTable seekTable = makeSeekTable();
if (masterStreamInfo == null) {
return;
}
// write FLAC marker
os.writeByteBlock(Constants.STREAM_SYNC_STRING, Constants.STREAM_SYNC_STRING.length);
// output StreamInfo
masterStreamInfo.write(os, false);
// output SeekTable
seekTable.write(os, true);
// generate output file
for (int i = 0; i < albumFiles.size(); i++) {
PackerFile aFile = (PackerFile) albumFiles.get(i);
appendMsg("Process file " + i + ": " + aFile.file);
try {
RandomAccessFile raf = new RandomAccessFile(aFile.file, "r");
raf.seek(aFile.firstFrameOffset);
for (int bytes = raf.read(buffer); bytes > 0; bytes = raf.read(buffer)) {
fos.write(buffer, 0, bytes);
}
fos.flush();
}
catch (FileNotFoundException e) {
appendMsg("File " + aFile.file + ": " + e);
}
catch (EOFException e) {
appendMsg("File " + aFile.file + ": Done!");
}
catch (IOException e) {
appendMsg("File " + aFile.file + ": " + e);
}
}
}
/**
* Main routine.
*
* @param args Command line arguments
*/
public static void main(String[] args) {
FlacPacker app = new FlacPacker("FLAC Album Maker");
app.show(true);
}
/**
* This class holds the fiels and their seek points.
*
* @author kc7bfi
*/
private class PackerFile {
protected File file;
protected SeekPoint seekPoint;
protected long firstFrameOffset;
/**
* The constructor.
*
* @param file The file
* @param seekPoint The SeekPoint
*/
public PackerFile(File file, SeekPoint seekPoint, long firstFrameOffset) {
this.file = file;
this.seekPoint = seekPoint;
this.firstFrameOffset = firstFrameOffset;
}
}
}