jaudiotagger update to maven
This commit is contained in:
parent
b9089f8653
commit
319f49b709
514 changed files with 18 additions and 76428 deletions
14
pom.xml
14
pom.xml
|
@ -142,6 +142,14 @@
|
|||
<id>maven-eclipse-repo</id>
|
||||
<url>http://maven-eclipse.github.io/maven</url>
|
||||
</repository>
|
||||
|
||||
|
||||
<repository>
|
||||
<id>jaudiotagger-repository</id>
|
||||
<url>https://dl.bintray.com/ijabz/maven</url>
|
||||
</repository>
|
||||
|
||||
|
||||
</repositories>
|
||||
|
||||
|
||||
|
@ -216,6 +224,12 @@
|
|||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.jthink</groupId>
|
||||
<artifactId>jaudiotagger</artifactId>
|
||||
<version>2.2.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.opencsv</groupId>
|
||||
<artifactId>opencsv</artifactId>
|
||||
|
|
|
@ -62,9 +62,9 @@
|
|||
</ul>
|
||||
|
||||
|
||||
<H3>Help</H3>
|
||||
<p>This may stop working at any time, due to changes in Audible systems or web site. If this happens, check for an update.</p>
|
||||
<p>No technical support is provided. Use at your own risk. Open Source additions are welcome. </p>
|
||||
<p>Suggestions welcome by sending issue or pull request.</p>
|
||||
<H3>Additional Help</H3>
|
||||
<p>This may stop working at any time, due to changes in Audible systems or web site. If this happens, check for an update or open issues.</p>
|
||||
<p>No technical support is provided. Open Source additions are welcome. </p>
|
||||
<p>Suggestions welcome by creating an issue or submitting a pull request.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
/**
|
||||
* @author : Paul Taylor
|
||||
* <p/>
|
||||
* Version @version:$Id: FileConstants.java 520 2008-01-01 15:16:38Z paultaylor $
|
||||
* <p/>
|
||||
* Jaudiotagger Copyright (C)2004,2005
|
||||
* <p/>
|
||||
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
* General Public License as published by the Free Software Foundation; either version 2.1 of the License,
|
||||
* or (at your option) any later version.
|
||||
* <p/>
|
||||
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Lesser General Public License for more details.
|
||||
* <p/>
|
||||
* You should have received a copy of the GNU Lesser General Public License along with this library; if not,
|
||||
* you can get a copy from http://www.opensource.org/licenses/lgpl-license.php or write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
* <p/>
|
||||
* Description:
|
||||
*/
|
||||
package org.jaudiotagger;
|
||||
|
||||
/**
|
||||
* Definitions of the bit used when reading file format from file
|
||||
*/
|
||||
public interface FileConstants {
|
||||
/**
|
||||
* defined for convenience
|
||||
*/
|
||||
int BIT7 = 0x80;
|
||||
/**
|
||||
* defined for convenience
|
||||
*/
|
||||
int BIT6 = 0x40;
|
||||
/**
|
||||
* defined for convenience
|
||||
*/
|
||||
int BIT5 = 0x20;
|
||||
/**
|
||||
* defined for convenience
|
||||
*/
|
||||
int BIT4 = 0x10;
|
||||
/**
|
||||
* defined for convenience
|
||||
*/
|
||||
int BIT3 = 0x08;
|
||||
/**
|
||||
* defined for convenience
|
||||
*/
|
||||
int BIT2 = 0x04;
|
||||
/**
|
||||
* defined for convenience
|
||||
*/
|
||||
int BIT1 = 0x02;
|
||||
/**
|
||||
* defined for convenience
|
||||
*/
|
||||
int BIT0 = 0x01;
|
||||
}
|
|
@ -1,303 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2003-2005 Raphaël Slinckx <raphael@slinckx.net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio;
|
||||
|
||||
import org.jaudiotagger.audio.aiff.AiffTag;
|
||||
import org.jaudiotagger.audio.exceptions.CannotWriteException;
|
||||
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
|
||||
import org.jaudiotagger.audio.flac.metadatablock.MetadataBlockDataPicture;
|
||||
import org.jaudiotagger.audio.real.RealTag;
|
||||
import org.jaudiotagger.audio.wav.WavTag;
|
||||
import org.jaudiotagger.logging.ErrorMessage;
|
||||
import org.jaudiotagger.tag.Tag;
|
||||
import org.jaudiotagger.tag.asf.AsfTag;
|
||||
import org.jaudiotagger.tag.flac.FlacTag;
|
||||
import org.jaudiotagger.tag.mp4.Mp4Tag;
|
||||
import org.jaudiotagger.tag.vorbiscomment.VorbisCommentTag;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.ArrayList;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* <p>This is the main object manipulated by the user representing an audiofile, its properties and its tag.</p>
|
||||
* <p>The prefered way to obtain an <code>AudioFile</code> is to use the <code>AudioFileIO.read(File)</code> method.</p>
|
||||
* <p>The <code>AudioFile</code> contains every properties associated with the file itself (no meta-data), like the bitrate, the sampling rate, the encoding audioHeaders, etc.</p>
|
||||
* <p>To get the meta-data contained in this file you have to get the <code>Tag</code> of this <code>AudioFile</code></p>
|
||||
*
|
||||
* @author Raphael Slinckx
|
||||
* @version $Id: AudioFile.java 1067 2012-10-17 18:01:32Z garymcgath $
|
||||
* @see AudioFileIO
|
||||
* @see Tag
|
||||
* @since v0.01
|
||||
*/
|
||||
public class AudioFile {
|
||||
//Logger
|
||||
public static Logger logger = Logger.getLogger("org.jaudiotagger.audio");
|
||||
|
||||
/**
|
||||
* The physical file that this instance represents.
|
||||
*/
|
||||
protected File file;
|
||||
|
||||
/**
|
||||
* The Audio header info
|
||||
*/
|
||||
protected AudioHeader audioHeader;
|
||||
|
||||
/**
|
||||
* The tag
|
||||
*/
|
||||
protected Tag tag;
|
||||
|
||||
public AudioFile() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>These constructors are used by the different readers, users should not use them, but use the <code>AudioFileIO.read(File)</code> method instead !.</p>
|
||||
* <p>Create the AudioFile representing file f, the encoding audio headers and containing the tag</p>
|
||||
*
|
||||
* @param f The file of the audio file
|
||||
* @param audioHeader the encoding audioHeaders over this file
|
||||
* @param tag the tag contained in this file or null if no tag exists
|
||||
*/
|
||||
public AudioFile(File f, AudioHeader audioHeader, Tag tag) {
|
||||
this.file = f;
|
||||
this.audioHeader = audioHeader;
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>These constructors are used by the different readers, users should not use them, but use the <code>AudioFileIO.read(File)</code> method instead !.</p>
|
||||
* <p>Create the AudioFile representing file denoted by pathnames, the encoding audio Headers and containing the tag</p>
|
||||
*
|
||||
* @param s The pathname of the audio file
|
||||
* @param audioHeader the encoding audioHeaders over this file
|
||||
* @param tag the tag contained in this file
|
||||
*/
|
||||
public AudioFile(String s, AudioHeader audioHeader, Tag tag) {
|
||||
this.file = new File(s);
|
||||
this.audioHeader = audioHeader;
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file
|
||||
* @return filename with audioFormat separator stripped off.
|
||||
*/
|
||||
public static String getBaseFilename(File file) {
|
||||
int index = file.getName().toLowerCase().lastIndexOf(".");
|
||||
if (index > 0) {
|
||||
return file.getName().substring(0, index);
|
||||
}
|
||||
return file.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Write the tag contained in this AudioFile in the actual file on the disk, this is the same as calling the <code>AudioFileIO.write(this)</code> method.</p>
|
||||
*
|
||||
* @throws CannotWriteException If the file could not be written/accessed, the extension wasn't recognized, or other IO error occured.
|
||||
* @see AudioFileIO
|
||||
*/
|
||||
public void commit() throws CannotWriteException {
|
||||
AudioFileIO.write(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the physical file
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the file to store the info in
|
||||
*
|
||||
* @param file
|
||||
*/
|
||||
public void setFile(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return audio header information
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public AudioHeader getAudioHeader() {
|
||||
return audioHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the tag contained in this AudioFile, the <code>Tag</code> contains any useful meta-data, like
|
||||
* artist, album, title, etc. If the file does not contain any tag the null is returned. Some audio formats do
|
||||
* not allow there to be no tag so in this case the reader would return an empty tag whereas for others such
|
||||
* as mp3 it is purely optional.
|
||||
*
|
||||
* @return Returns the tag contained in this AudioFile, or null if no tag exists.
|
||||
*/
|
||||
public Tag getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a tag to this audio file
|
||||
*
|
||||
* @param tag Tag to be assigned
|
||||
*/
|
||||
public void setTag(Tag tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a multi-line string with the file path, the encoding audioHeader, and the tag contents.</p>
|
||||
*
|
||||
* @return A multi-line string with the file path, the encoding audioHeader, and the tag contents.
|
||||
* TODO Maybe this can be changed ?
|
||||
*/
|
||||
public String toString() {
|
||||
return "AudioFile " + getFile().getAbsolutePath()
|
||||
+ " --------\n" + audioHeader.toString() + "\n" + ((tag == null) ? "" : tag.toString()) + "\n-------------------";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check does file exist
|
||||
*
|
||||
* @param file
|
||||
* @throws FileNotFoundException if file not found
|
||||
*/
|
||||
public void checkFileExists(File file) throws FileNotFoundException {
|
||||
logger.config("Reading file:" + "path" + file.getPath() + ":abs:" + file.getAbsolutePath());
|
||||
if (!file.exists()) {
|
||||
logger.severe("Unable to find:" + file.getPath());
|
||||
throw new FileNotFoundException(ErrorMessage.UNABLE_TO_FIND_FILE.getMsg(file.getPath()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the file is accessible with the correct permissions, otherwise exception occurs
|
||||
*
|
||||
* @param file
|
||||
* @param readOnly
|
||||
* @return
|
||||
* @throws ReadOnlyFileException
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
protected RandomAccessFile checkFilePermissions(File file, boolean readOnly) throws ReadOnlyFileException, FileNotFoundException {
|
||||
RandomAccessFile newFile;
|
||||
|
||||
checkFileExists(file);
|
||||
|
||||
// Unless opened as readonly the file must be writable
|
||||
if (readOnly) {
|
||||
newFile = new RandomAccessFile(file, "r");
|
||||
} else {
|
||||
if (!file.canWrite()) {
|
||||
logger.severe("Unable to write:" + file.getPath());
|
||||
throw new ReadOnlyFileException(ErrorMessage.NO_PERMISSIONS_TO_WRITE_TO_FILE.getMsg(file.getPath()));
|
||||
}
|
||||
newFile = new RandomAccessFile(file, "rws");
|
||||
}
|
||||
return newFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional debugging method. Must override to do anything interesting.
|
||||
*
|
||||
* @return Empty string.
|
||||
*/
|
||||
public String displayStructureAsXML() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional debugging method. Must override to do anything interesting.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String displayStructureAsPlainText() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Default Tag
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Tag createDefaultTag() {
|
||||
if (SupportedFileFormat.FLAC.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||
return new FlacTag(VorbisCommentTag.createNewTag(), new ArrayList<MetadataBlockDataPicture>());
|
||||
} else if (SupportedFileFormat.OGG.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||
return VorbisCommentTag.createNewTag();
|
||||
} else if (SupportedFileFormat.MP4.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||
return new Mp4Tag();
|
||||
} else if (SupportedFileFormat.M4A.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||
return new Mp4Tag();
|
||||
} else if (SupportedFileFormat.M4P.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||
return new Mp4Tag();
|
||||
} else if (SupportedFileFormat.WMA.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||
return new AsfTag();
|
||||
} else if (SupportedFileFormat.WAV.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||
return new WavTag();
|
||||
} else if (SupportedFileFormat.RA.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||
return new RealTag();
|
||||
} else if (SupportedFileFormat.RM.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||
return new RealTag();
|
||||
} else if (SupportedFileFormat.AIF.getFilesuffix().equals(file.getName().substring(file.getName().lastIndexOf('.')))) {
|
||||
return new AiffTag();
|
||||
} else {
|
||||
throw new RuntimeException("Unable to create default tag for this file format");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tag or if the file doesn't have one at all, create a default tag and return
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Tag getTagOrCreateDefault() {
|
||||
Tag tag = getTag();
|
||||
if (tag == null) {
|
||||
return createDefaultTag();
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tag or if the file doesn't have one at all, create a default tag and set it
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Tag getTagOrCreateAndSetDefault() {
|
||||
Tag tag = getTagOrCreateDefault();
|
||||
setTag(tag);
|
||||
return tag;
|
||||
}
|
||||
|
||||
public Tag getTagAndConvertOrCreateAndSetDefault() {
|
||||
return getTagOrCreateAndSetDefault();
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2003-2005 Raphaël Slinckx <raphael@slinckx.net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio;
|
||||
|
||||
import org.jaudiotagger.audio.generic.Utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
|
||||
/**
|
||||
* <p>This is a simple FileFilter that will only allow the file supported by this library.</p>
|
||||
* <p>It will also accept directories. An additional condition is that file must be readable (read permission) and
|
||||
* are not hidden (dot files, or hidden files)</p>
|
||||
*
|
||||
* @author Raphael Slinckx
|
||||
* @version $Id: AudioFileFilter.java 836 2009-11-12 15:44:07Z paultaylor $
|
||||
* @since v0.01
|
||||
*/
|
||||
public class AudioFileFilter implements FileFilter {
|
||||
/**
|
||||
* allows Directories
|
||||
*/
|
||||
private final boolean allowDirectories;
|
||||
|
||||
public AudioFileFilter(boolean allowDirectories) {
|
||||
this.allowDirectories = allowDirectories;
|
||||
}
|
||||
|
||||
public AudioFileFilter() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Check whether the given file meet the required conditions (supported by the library OR directory).
|
||||
* The File must also be readable and not hidden.</p>
|
||||
*
|
||||
* @param f The file to test
|
||||
* @return a boolean indicating if the file is accepted or not
|
||||
*/
|
||||
public boolean accept(File f) {
|
||||
if (f.isHidden() || !f.canRead()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (f.isDirectory()) {
|
||||
return allowDirectories;
|
||||
}
|
||||
|
||||
String ext = Utils.getExtension(f);
|
||||
|
||||
try {
|
||||
if (SupportedFileFormat.valueOf(ext.toUpperCase()) != null) {
|
||||
return true;
|
||||
}
|
||||
} catch (IllegalArgumentException iae) {
|
||||
//Not known enum value
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,317 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2003-2005 Raphaël Slinckx <raphael@slinckx.net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio;
|
||||
|
||||
import org.jaudiotagger.audio.aiff.AiffFileReader;
|
||||
import org.jaudiotagger.audio.asf.AsfFileReader;
|
||||
import org.jaudiotagger.audio.asf.AsfFileWriter;
|
||||
import org.jaudiotagger.audio.exceptions.CannotReadException;
|
||||
import org.jaudiotagger.audio.exceptions.CannotWriteException;
|
||||
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
|
||||
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
|
||||
import org.jaudiotagger.audio.flac.FlacFileReader;
|
||||
import org.jaudiotagger.audio.flac.FlacFileWriter;
|
||||
import org.jaudiotagger.audio.generic.*;
|
||||
import org.jaudiotagger.audio.mp3.MP3FileReader;
|
||||
import org.jaudiotagger.audio.mp3.MP3FileWriter;
|
||||
import org.jaudiotagger.audio.mp4.Mp4FileReader;
|
||||
import org.jaudiotagger.audio.mp4.Mp4FileWriter;
|
||||
import org.jaudiotagger.audio.ogg.OggFileReader;
|
||||
import org.jaudiotagger.audio.ogg.OggFileWriter;
|
||||
import org.jaudiotagger.audio.real.RealFileReader;
|
||||
import org.jaudiotagger.audio.wav.WavFileReader;
|
||||
import org.jaudiotagger.audio.wav.WavFileWriter;
|
||||
import org.jaudiotagger.logging.ErrorMessage;
|
||||
import org.jaudiotagger.tag.TagException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* <p/>
|
||||
* The main entry point for the Tag Reading/Writing operations, this class will
|
||||
* select the appropriate reader/writer for the given file.
|
||||
* </p>
|
||||
* <p/>
|
||||
* It selects the appropriate reader/writer based on the file extension (case
|
||||
* ignored).
|
||||
* </p>
|
||||
* <p/>
|
||||
* Here is an simple example of use:
|
||||
* </p>
|
||||
* <p/>
|
||||
* <code>
|
||||
* AudioFile audioFile = AudioFileIO.read(new File("audiofile.mp3")); //Reads the given file.<br/>
|
||||
* int bitrate = audioFile.getBitrate(); //Retreives the bitrate of the file.<br/>
|
||||
* String artist = audioFile.getTag().getFirst(TagFieldKey.ARTIST); //Retreive the artist name.<br/>
|
||||
* audioFile.getTag().setGenre("Progressive Rock"); //Sets the genre to Prog. Rock, note the file on disk is still unmodified.<br/>
|
||||
* AudioFileIO.write(audioFile); //Write the modifications in the file on disk.
|
||||
* </code>
|
||||
* </p>
|
||||
* <p/>
|
||||
* You can also use the <code>commit()</code> method defined for
|
||||
* <code>AudioFile</code>s to achieve the same goal as
|
||||
* <code>AudioFileIO.write(File)</code>, like this:
|
||||
* </p>
|
||||
* <p/>
|
||||
* <code>
|
||||
* AudioFile audioFile = AudioFileIO.read(new File("audiofile.mp3"));<br/>
|
||||
* audioFile.getTag().setGenre("Progressive Rock");<br/>
|
||||
* audioFile.commit(); //Write the modifications in the file on disk.<br/>
|
||||
* </code>
|
||||
* </p>
|
||||
*
|
||||
* @author Raphael Slinckx
|
||||
* @version $Id: AudioFileIO.java 1067 2012-10-17 18:01:32Z garymcgath $
|
||||
* @see AudioFile
|
||||
* @see org.jaudiotagger.tag.Tag
|
||||
* @since v0.01
|
||||
*/
|
||||
public class AudioFileIO {
|
||||
|
||||
//Logger
|
||||
public static Logger logger = Logger.getLogger("org.jaudiotagger.audio");
|
||||
|
||||
// !! Do not forget to also add new supported extensions to AudioFileFilter
|
||||
// !!
|
||||
|
||||
/**
|
||||
* This field contains the default instance for static use.
|
||||
*/
|
||||
private static AudioFileIO defaultInstance;
|
||||
/**
|
||||
* This member is used to broadcast modification events to registered
|
||||
*/
|
||||
private final ModificationHandler modificationHandler;
|
||||
// These tables contains all the readers/writers associated with extension
|
||||
// as a key
|
||||
private Map<String, AudioFileReader> readers = new HashMap<String, AudioFileReader>();
|
||||
private Map<String, AudioFileWriter> writers = new HashMap<String, AudioFileWriter>();
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*/
|
||||
public AudioFileIO() {
|
||||
this.modificationHandler = new ModificationHandler();
|
||||
prepareReadersAndWriters();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p/>
|
||||
* Delete the tag, if any, contained in the given file.
|
||||
* </p>
|
||||
*
|
||||
* @param f The file where the tag will be deleted
|
||||
* @throws org.jaudiotagger.audio.exceptions.CannotWriteException If the file could not be written/accessed, the extension
|
||||
* wasn't recognized, or other IO error occurred.
|
||||
* @throws org.jaudiotagger.audio.exceptions.CannotReadException
|
||||
*/
|
||||
public static void delete(AudioFile f) throws CannotReadException, CannotWriteException {
|
||||
getDefaultAudioFileIO().deleteTag(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the default instance for static use.<br>
|
||||
*
|
||||
* @return The default instance.
|
||||
*/
|
||||
public static AudioFileIO getDefaultAudioFileIO() {
|
||||
if (defaultInstance == null) {
|
||||
defaultInstance = new AudioFileIO();
|
||||
}
|
||||
return defaultInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p/>
|
||||
* Read the tag contained in the given file.
|
||||
* </p>
|
||||
*
|
||||
* @param f The file to read.
|
||||
* @return The AudioFile with the file tag and the file encoding info.
|
||||
* @throws org.jaudiotagger.audio.exceptions.CannotReadException If the file could not be read, the extension wasn't
|
||||
* recognized, or an IO error occurred during the read.
|
||||
* @throws org.jaudiotagger.tag.TagException
|
||||
* @throws org.jaudiotagger.audio.exceptions.ReadOnlyFileException
|
||||
* @throws java.io.IOException
|
||||
* @throws org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
|
||||
*/
|
||||
public static AudioFile read(File f)
|
||||
throws CannotReadException, IOException, TagException, ReadOnlyFileException, InvalidAudioFrameException {
|
||||
return getDefaultAudioFileIO().readFile(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p/>
|
||||
* Write the tag contained in the audioFile in the actual file on the disk.
|
||||
* </p>
|
||||
*
|
||||
* @param f The AudioFile to be written
|
||||
* @throws CannotWriteException If the file could not be written/accessed, the extension
|
||||
* wasn't recognized, or other IO error occurred.
|
||||
*/
|
||||
public static void write(AudioFile f) throws CannotWriteException {
|
||||
getDefaultAudioFileIO().writeFile(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an listener for all file formats.
|
||||
*
|
||||
* @param listener listener
|
||||
*/
|
||||
public void addAudioFileModificationListener(
|
||||
AudioFileModificationListener listener) {
|
||||
this.modificationHandler.addAudioFileModificationListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p/>
|
||||
* Delete the tag, if any, contained in the given file.
|
||||
* </p>
|
||||
*
|
||||
* @param f The file where the tag will be deleted
|
||||
* @throws org.jaudiotagger.audio.exceptions.CannotWriteException If the file could not be written/accessed, the extension
|
||||
* wasn't recognized, or other IO error occurred.
|
||||
* @throws org.jaudiotagger.audio.exceptions.CannotReadException
|
||||
*/
|
||||
public void deleteTag(AudioFile f) throws CannotReadException, CannotWriteException {
|
||||
String ext = Utils.getExtension(f.getFile());
|
||||
|
||||
Object afw = writers.get(ext);
|
||||
if (afw == null) {
|
||||
throw new CannotWriteException(ErrorMessage.NO_DELETER_FOR_THIS_FORMAT.getMsg(ext));
|
||||
}
|
||||
|
||||
((AudioFileWriter) afw).delete(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the readers and writers.
|
||||
*/
|
||||
private void prepareReadersAndWriters() {
|
||||
|
||||
// Tag Readers
|
||||
readers.put(SupportedFileFormat.OGG.getFilesuffix(), new OggFileReader());
|
||||
readers.put(SupportedFileFormat.FLAC.getFilesuffix(), new FlacFileReader());
|
||||
readers.put(SupportedFileFormat.MP3.getFilesuffix(), new MP3FileReader());
|
||||
readers.put(SupportedFileFormat.MP4.getFilesuffix(), new Mp4FileReader());
|
||||
readers.put(SupportedFileFormat.M4A.getFilesuffix(), new Mp4FileReader());
|
||||
readers.put(SupportedFileFormat.M4P.getFilesuffix(), new Mp4FileReader());
|
||||
readers.put(SupportedFileFormat.M4B.getFilesuffix(), new Mp4FileReader());
|
||||
readers.put(SupportedFileFormat.WAV.getFilesuffix(), new WavFileReader());
|
||||
readers.put(SupportedFileFormat.WMA.getFilesuffix(), new AsfFileReader());
|
||||
readers.put(SupportedFileFormat.AIF.getFilesuffix(), new AiffFileReader());
|
||||
final RealFileReader realReader = new RealFileReader();
|
||||
readers.put(SupportedFileFormat.RA.getFilesuffix(), realReader);
|
||||
readers.put(SupportedFileFormat.RM.getFilesuffix(), realReader);
|
||||
|
||||
// Tag Writers
|
||||
writers.put(SupportedFileFormat.OGG.getFilesuffix(), new OggFileWriter());
|
||||
writers.put(SupportedFileFormat.FLAC.getFilesuffix(), new FlacFileWriter());
|
||||
writers.put(SupportedFileFormat.MP3.getFilesuffix(), new MP3FileWriter());
|
||||
writers.put(SupportedFileFormat.MP4.getFilesuffix(), new Mp4FileWriter());
|
||||
writers.put(SupportedFileFormat.M4A.getFilesuffix(), new Mp4FileWriter());
|
||||
writers.put(SupportedFileFormat.M4P.getFilesuffix(), new Mp4FileWriter());
|
||||
writers.put(SupportedFileFormat.M4B.getFilesuffix(), new Mp4FileWriter());
|
||||
writers.put(SupportedFileFormat.WAV.getFilesuffix(), new WavFileWriter());
|
||||
writers.put(SupportedFileFormat.WMA.getFilesuffix(), new AsfFileWriter());
|
||||
|
||||
// Register modificationHandler
|
||||
Iterator<AudioFileWriter> it = writers.values().iterator();
|
||||
for (AudioFileWriter curr : writers.values()) {
|
||||
curr.setAudioFileModificationListener(this.modificationHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p/>
|
||||
* Read the tag contained in the given file.
|
||||
* </p>
|
||||
*
|
||||
* @param f The file to read.
|
||||
* @return The AudioFile with the file tag and the file encoding info.
|
||||
* @throws org.jaudiotagger.audio.exceptions.CannotReadException If the file could not be read, the extension wasn't
|
||||
* recognized, or an IO error occurred during the read.
|
||||
* @throws org.jaudiotagger.tag.TagException
|
||||
* @throws org.jaudiotagger.audio.exceptions.ReadOnlyFileException
|
||||
* @throws java.io.IOException
|
||||
* @throws org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
|
||||
*/
|
||||
public AudioFile readFile(File f)
|
||||
throws CannotReadException, IOException, TagException, ReadOnlyFileException, InvalidAudioFrameException {
|
||||
checkFileExists(f);
|
||||
String ext = Utils.getExtension(f);
|
||||
|
||||
AudioFileReader afr = readers.get(ext);
|
||||
if (afr == null) {
|
||||
throw new CannotReadException(ErrorMessage.NO_READER_FOR_THIS_FORMAT.getMsg(ext));
|
||||
}
|
||||
|
||||
return afr.read(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check does file exist
|
||||
*
|
||||
* @param file
|
||||
* @throws java.io.FileNotFoundException
|
||||
*/
|
||||
public void checkFileExists(File file) throws FileNotFoundException {
|
||||
logger.config("Reading file:" + "path" + file.getPath() + ":abs:" + file.getAbsolutePath());
|
||||
if (!file.exists()) {
|
||||
logger.severe("Unable to find:" + file.getPath());
|
||||
throw new FileNotFoundException(ErrorMessage.UNABLE_TO_FIND_FILE.getMsg(file.getPath()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener for all file formats.
|
||||
*
|
||||
* @param listener listener
|
||||
*/
|
||||
public void removeAudioFileModificationListener(
|
||||
AudioFileModificationListener listener) {
|
||||
this.modificationHandler.removeAudioFileModificationListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p/>
|
||||
* Write the tag contained in the audioFile in the actual file on the disk.
|
||||
* </p>
|
||||
*
|
||||
* @param f The AudioFile to be written
|
||||
* @throws CannotWriteException If the file could not be written/accessed, the extension
|
||||
* wasn't recognized, or other IO error occurred.
|
||||
*/
|
||||
public void writeFile(AudioFile f) throws CannotWriteException {
|
||||
String ext = Utils.getExtension(f.getFile());
|
||||
|
||||
AudioFileWriter afw = writers.get(ext);
|
||||
if (afw == null) {
|
||||
throw new CannotWriteException(ErrorMessage.NO_WRITER_FOR_THIS_FORMAT.getMsg(ext));
|
||||
}
|
||||
|
||||
afw.write(f);
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
package org.jaudiotagger.audio;
|
||||
|
||||
/**
|
||||
* Representation of AudioHeader
|
||||
* <p/>
|
||||
* <p>Contains info about the Audio Header
|
||||
*/
|
||||
public interface AudioHeader {
|
||||
/**
|
||||
* @return the audio file type
|
||||
*/
|
||||
String getEncodingType();
|
||||
|
||||
/**
|
||||
* @return the BitRate of the Audio
|
||||
*/
|
||||
String getBitRate();
|
||||
|
||||
/**
|
||||
* @return birate as a number
|
||||
*/
|
||||
long getBitRateAsNumber();
|
||||
|
||||
|
||||
/**
|
||||
* @return the Sampling rate
|
||||
*/
|
||||
String getSampleRate();
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
int getSampleRateAsNumber();
|
||||
|
||||
/**
|
||||
* @return the format
|
||||
*/
|
||||
String getFormat();
|
||||
|
||||
/**
|
||||
* @return the Channel Mode such as Stereo or Mono
|
||||
*/
|
||||
String getChannels();
|
||||
|
||||
/**
|
||||
* @return if the bitRate is variable
|
||||
*/
|
||||
boolean isVariableBitRate();
|
||||
|
||||
/**
|
||||
* @return track length
|
||||
*/
|
||||
int getTrackLength();
|
||||
|
||||
/**
|
||||
* @return the number of bits per sample
|
||||
*/
|
||||
int getBitsPerSample();
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
boolean isLossless();
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package org.jaudiotagger.audio;
|
||||
|
||||
/**
|
||||
* Files formats currently supported by Library.
|
||||
* Each enum value is associated with a file suffix (extension).
|
||||
*/
|
||||
public enum SupportedFileFormat {
|
||||
OGG("ogg"),
|
||||
MP3("mp3"),
|
||||
FLAC("flac"),
|
||||
MP4("mp4"),
|
||||
M4A("m4a"),
|
||||
M4P("m4p"),
|
||||
WMA("wma"),
|
||||
WAV("wav"),
|
||||
RA("ra"),
|
||||
RM("rm"),
|
||||
M4B("m4b"),
|
||||
AIF("aif");
|
||||
|
||||
private String filesuffix;
|
||||
|
||||
/**
|
||||
* Constructor for internal use by this enum.
|
||||
*/
|
||||
SupportedFileFormat(String filesuffix) {
|
||||
this.filesuffix = filesuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file suffix (lower case without initial .) associated with the format.
|
||||
*/
|
||||
public String getFilesuffix() {
|
||||
return filesuffix;
|
||||
}
|
||||
}
|
|
@ -1,179 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import org.jaudiotagger.audio.generic.GenericAudioHeader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Non-"tag" metadata from the AIFF file. In general, read-only.
|
||||
*/
|
||||
public class AiffAudioHeader extends GenericAudioHeader {
|
||||
|
||||
private FileType fileType;
|
||||
private Date timestamp;
|
||||
private Endian endian;
|
||||
private String audioEncoding;
|
||||
private String name;
|
||||
private String author;
|
||||
private String copyright;
|
||||
private List<String> applicationIdentifiers;
|
||||
private List<String> comments;
|
||||
|
||||
public AiffAudioHeader() {
|
||||
applicationIdentifiers = new ArrayList<String>();
|
||||
comments = new ArrayList<String>();
|
||||
endian = Endian.BIG_ENDIAN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the timestamp of the file.
|
||||
*/
|
||||
public Date getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timestamp.
|
||||
*/
|
||||
public void setTimestamp(Date d) {
|
||||
timestamp = d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file type (AIFF or AIFC)
|
||||
*/
|
||||
public FileType getFileType() {
|
||||
return fileType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the file type (AIFF or AIFC)
|
||||
*/
|
||||
public void setFileType(FileType typ) {
|
||||
fileType = typ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the author
|
||||
*/
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the author
|
||||
*/
|
||||
public void setAuthor(String a) {
|
||||
author = a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name. May be null.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name
|
||||
*/
|
||||
public void setName(String n) {
|
||||
name = n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the copyright. May be null.
|
||||
*/
|
||||
public String getCopyright() {
|
||||
return copyright;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the copyright
|
||||
*/
|
||||
public void setCopyright(String c) {
|
||||
copyright = c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return endian status (big or little)
|
||||
*/
|
||||
public Endian getEndian() {
|
||||
return endian;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set endian status (big or little)
|
||||
*/
|
||||
public void setEndian(Endian e) {
|
||||
endian = e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of all application identifiers
|
||||
*/
|
||||
public List<String> getApplicationIdentifiers() {
|
||||
return applicationIdentifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an application identifier. There can be any number of these.
|
||||
*/
|
||||
public void addApplicationIdentifier(String id) {
|
||||
applicationIdentifiers.add(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of all annotations
|
||||
*/
|
||||
public List<String> getAnnotations() {
|
||||
return applicationIdentifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an annotation. There can be any number of these.
|
||||
*/
|
||||
public void addAnnotation(String a) {
|
||||
applicationIdentifiers.add(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of all comments
|
||||
*/
|
||||
public List<String> getComments() {
|
||||
return comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a comment. There can be any number of these.
|
||||
*/
|
||||
public void addComment(String c) {
|
||||
comments.add(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the audio encoding as a descriptive string
|
||||
*/
|
||||
public String getAudioEncoding() {
|
||||
return audioEncoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the audio encoding as a descriptive string
|
||||
*/
|
||||
public void setAudioEncoding(String s) {
|
||||
audioEncoding = s;
|
||||
}
|
||||
|
||||
public enum FileType {
|
||||
AIFFTYPE,
|
||||
AIFCTYPE
|
||||
}
|
||||
|
||||
public enum Endian {
|
||||
BIG_ENDIAN,
|
||||
LITTLE_ENDIAN
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import org.jaudiotagger.audio.AudioFile;
|
||||
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
|
||||
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
|
||||
import org.jaudiotagger.tag.TagException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class AiffFile extends AudioFile {
|
||||
|
||||
/**
|
||||
* A static DateFormat object for generating ISO date strings
|
||||
*/
|
||||
public final static SimpleDateFormat ISO_DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
|
||||
|
||||
/**
|
||||
* Creates a new empty AiffFile that is not associated with a
|
||||
* specific file.
|
||||
*/
|
||||
public AiffFile() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new MP3File datatype and parse the tag from the given filename.
|
||||
*
|
||||
* @param filename AIFF file
|
||||
* @throws IOException on any I/O error
|
||||
* @throws TagException on any exception generated by this library.
|
||||
* @throws org.jaudiotagger.audio.exceptions.ReadOnlyFileException
|
||||
* @throws org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
|
||||
*/
|
||||
public AiffFile(String filename) throws
|
||||
IOException, TagException, ReadOnlyFileException, InvalidAudioFrameException {
|
||||
this(new File(filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MP3File datatype and parse the tag from the given file
|
||||
* Object.
|
||||
*
|
||||
* @param file MP3 file
|
||||
* @throws IOException on any I/O error
|
||||
* @throws TagException on any exception generated by this library.
|
||||
* @throws org.jaudiotagger.audio.exceptions.ReadOnlyFileException
|
||||
* @throws org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
|
||||
*/
|
||||
public AiffFile(File file)
|
||||
throws IOException, TagException, ReadOnlyFileException, InvalidAudioFrameException {
|
||||
this(file, true);
|
||||
}
|
||||
|
||||
public AiffFile(File file, boolean readOnly)
|
||||
throws IOException, TagException, ReadOnlyFileException, InvalidAudioFrameException {
|
||||
RandomAccessFile newFile = null;
|
||||
try {
|
||||
logger.setLevel(Level.FINEST);
|
||||
logger.fine("Called AiffFile constructor on " + file.getAbsolutePath());
|
||||
this.file = file;
|
||||
|
||||
//Check File accessibility
|
||||
newFile = checkFilePermissions(file, readOnly);
|
||||
audioHeader = new AiffAudioHeader();
|
||||
//readTag();
|
||||
|
||||
} finally {
|
||||
if (newFile != null) {
|
||||
newFile.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AiffAudioHeader getAiffAudioHeader() {
|
||||
return (AiffAudioHeader) audioHeader;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import org.jaudiotagger.audio.exceptions.CannotReadException;
|
||||
import org.jaudiotagger.audio.generic.AudioFileReader;
|
||||
import org.jaudiotagger.audio.generic.GenericAudioHeader;
|
||||
import org.jaudiotagger.tag.Tag;
|
||||
import org.jaudiotagger.tag.aiff.AiffTag;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
public class AiffFileReader extends AudioFileReader {
|
||||
|
||||
/* Fixed value for first 4 bytes */
|
||||
private static final int[] sigByte =
|
||||
{0X46, 0X4F, 0X52, 0X4D};
|
||||
|
||||
/* AIFF-specific information which isn't "tag" information */
|
||||
private AiffAudioHeader aiffHeader;
|
||||
|
||||
/* "Tag" information */
|
||||
private AiffTag aiffTag;
|
||||
|
||||
/* InputStream that reads the file sequentially */
|
||||
// private DataInputStream inStream;
|
||||
|
||||
public AiffFileReader() {
|
||||
aiffHeader = new AiffAudioHeader();
|
||||
aiffTag = new AiffTag();
|
||||
}
|
||||
|
||||
|
||||
public AiffFileReader(RandomAccessFile raf) {
|
||||
aiffHeader = new AiffAudioHeader();
|
||||
aiffTag = new AiffTag();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads the file and fills in the audio header and tag information.
|
||||
* Holds the tag information for later and returns the audio header.
|
||||
*/
|
||||
@Override
|
||||
protected GenericAudioHeader getEncodingInfo(RandomAccessFile raf)
|
||||
throws CannotReadException, IOException {
|
||||
logger.finest("Reading AIFF file ");
|
||||
byte sigBuf[] = new byte[4];
|
||||
raf.read(sigBuf);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (sigBuf[i] != sigByte[i]) {
|
||||
logger.finest("AIFF file has incorrect signature");
|
||||
throw new CannotReadException("Not an AIFF file: incorrect signature");
|
||||
}
|
||||
}
|
||||
long bytesRemaining = AiffUtil.readUINT32(raf);
|
||||
|
||||
// Read the file type.
|
||||
if (!readFileType(raf)) {
|
||||
throw new CannotReadException("Invalid AIFF file: Incorrect file type info");
|
||||
}
|
||||
bytesRemaining -= 4;
|
||||
while (bytesRemaining > 0) {
|
||||
if (!readChunk(raf, bytesRemaining)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return aiffHeader;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Tag getTag(RandomAccessFile raf) throws CannotReadException,
|
||||
IOException {
|
||||
logger.info("getTag called");
|
||||
|
||||
// TODO fill out stub code
|
||||
return aiffTag;
|
||||
|
||||
}
|
||||
|
||||
/* Reads the file type.
|
||||
* Broken out from parse().
|
||||
* If it is not a valid file type, returns false.
|
||||
*/
|
||||
private boolean readFileType(RandomAccessFile raf) throws IOException {
|
||||
String typ = AiffUtil.read4Chars(raf);
|
||||
if ("AIFF".equals(typ)) {
|
||||
aiffHeader.setFileType(AiffAudioHeader.FileType.AIFFTYPE);
|
||||
return true;
|
||||
} else if ("AIFC".equals(typ)) {
|
||||
aiffHeader.setFileType(AiffAudioHeader.FileType.AIFCTYPE);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an AIFF Chunk.
|
||||
*/
|
||||
protected boolean readChunk
|
||||
(RandomAccessFile raf, long bytesRemaining)
|
||||
throws IOException {
|
||||
Chunk chunk = null;
|
||||
ChunkHeader chunkh = new ChunkHeader();
|
||||
if (!chunkh.readHeader(raf)) {
|
||||
return false;
|
||||
}
|
||||
int chunkSize = (int) chunkh.getSize();
|
||||
bytesRemaining -= chunkSize + 8;
|
||||
|
||||
String id = chunkh.getID();
|
||||
if ("FVER".equals(id)) {
|
||||
chunk = new FormatVersionChunk(chunkh, raf, aiffHeader);
|
||||
} else if ("APPL".equals(id)) {
|
||||
chunk = new ApplicationChunk(chunkh, raf, aiffHeader);
|
||||
// Any number of application chunks is ok
|
||||
} else if ("COMM".equals(id)) {
|
||||
// There should be no more than one of these
|
||||
chunk = new CommonChunk(chunkh, raf, aiffHeader);
|
||||
} else if ("COMT".equals(id)) {
|
||||
chunk = new CommentsChunk(chunkh, raf, aiffHeader);
|
||||
} else if ("NAME".equals(id)) {
|
||||
chunk = new NameChunk(chunkh, raf, aiffHeader);
|
||||
} else if ("AUTH".equals(id)) {
|
||||
chunk = new AuthorChunk(chunkh, raf, aiffHeader);
|
||||
} else if ("(c) ".equals(id)) {
|
||||
chunk = new CopyrightChunk(chunkh, raf, aiffHeader);
|
||||
} else if ("ANNO".equals(id)) {
|
||||
chunk = new AnnotationChunk(chunkh, raf, aiffHeader);
|
||||
} else if ("ID3 ".equals(id)) {
|
||||
chunk = new ID3Chunk(chunkh, raf, aiffTag);
|
||||
}
|
||||
|
||||
if (chunk != null) {
|
||||
if (!chunk.readChunk()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Other chunk types are legal, just skip over them
|
||||
raf.skipBytes(chunkSize);
|
||||
}
|
||||
if ((chunkSize & 1) != 0) {
|
||||
// Must come out to an even byte boundary
|
||||
raf.skipBytes(1);
|
||||
--bytesRemaining;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import org.jaudiotagger.audio.exceptions.CannotReadException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
|
||||
/**
|
||||
* Functions for reading an AIFF file
|
||||
*/
|
||||
public class AiffInfoReader {
|
||||
|
||||
public AiffAudioHeader read(RandomAccessFile raf) throws CannotReadException, IOException {
|
||||
if (raf.length() < 4) {
|
||||
//Empty File
|
||||
throw new CannotReadException("Not an AIFF file; too short");
|
||||
}
|
||||
return null; // TODO stub
|
||||
}
|
||||
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
/**
|
||||
* This class implements an InputStream over a RandomAccessFile for the
|
||||
* sake of efficiency. The AIFF reader uses a RandomAccessFile for
|
||||
* consistency with the other modules, but really just reads the file
|
||||
* sequentially. This class permits reasonable buffering.
|
||||
*/
|
||||
public class AiffInputStream extends InputStream {
|
||||
|
||||
private final static int BUFSIZE = 2048;
|
||||
/**
|
||||
* The underlying file
|
||||
*/
|
||||
private RandomAccessFile raf;
|
||||
|
||||
/**
|
||||
* The input buffer
|
||||
*/
|
||||
private byte[] fileBuf;
|
||||
|
||||
/**
|
||||
* Number of valid bytes in the input buffer
|
||||
*/
|
||||
private int fileBufSize;
|
||||
|
||||
/**
|
||||
* Current position in the input buffer
|
||||
*/
|
||||
private int fileBufOffset;
|
||||
|
||||
/**
|
||||
* End of file flag
|
||||
*/
|
||||
private boolean eof;
|
||||
|
||||
public AiffInputStream(RandomAccessFile raf) {
|
||||
this.raf = raf;
|
||||
eof = false;
|
||||
fileBuf = new byte[BUFSIZE];
|
||||
fileBufSize = 0;
|
||||
fileBufOffset = 0;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
for (; ; ) {
|
||||
if (eof) {
|
||||
return -1;
|
||||
}
|
||||
if (fileBufOffset < fileBufSize) {
|
||||
return ((int) fileBuf[fileBufOffset++]) & 0XFF;
|
||||
} else {
|
||||
fillBuf();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Refill the buffer */
|
||||
private void fillBuf() throws IOException {
|
||||
int bytesRead = raf.read(fileBuf, 0, BUFSIZE);
|
||||
fileBufOffset = 0;
|
||||
fileBufSize = bytesRead;
|
||||
if (fileBufSize == 0) {
|
||||
eof = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,166 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import org.jaudiotagger.audio.generic.GenericTag;
|
||||
import org.jaudiotagger.audio.generic.Utils;
|
||||
import org.jaudiotagger.tag.FieldDataInvalidException;
|
||||
import org.jaudiotagger.tag.KeyNotFoundException;
|
||||
import org.jaudiotagger.tag.TagField;
|
||||
import org.jaudiotagger.tag.TagTextField;
|
||||
|
||||
public class AiffTag extends GenericTag {
|
||||
|
||||
public boolean hasField(AiffTagFieldKey fieldKey) {
|
||||
return hasField(fieldKey.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new AIFF-specific field and set it in the tag
|
||||
*
|
||||
* @param genericKey
|
||||
* @param value
|
||||
* @throws KeyNotFoundException
|
||||
* @throws FieldDataInvalidException
|
||||
*/
|
||||
public void setField(AiffTagFieldKey genericKey, String value) throws KeyNotFoundException, FieldDataInvalidException {
|
||||
TagField tagfield = createField(genericKey, value);
|
||||
setField(tagfield);
|
||||
}
|
||||
|
||||
public TagField createField(AiffTagFieldKey genericKey, String value) throws KeyNotFoundException, FieldDataInvalidException {
|
||||
return new AiffTagTextField(genericKey.name(), value);
|
||||
}
|
||||
|
||||
private class AiffTagTextField implements TagTextField {
|
||||
|
||||
/**
|
||||
* Stores the identifier.
|
||||
*/
|
||||
private final String id;
|
||||
/**
|
||||
* Stores the string.
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param fieldId The identifier.
|
||||
* @param initialContent The string.
|
||||
*/
|
||||
public AiffTagTextField(String fieldId, String initialContent) {
|
||||
this.id = fieldId;
|
||||
this.content = initialContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see org.jaudiotagger.tag.TagField#copyContent(org.jaudiotagger.tag.TagField)
|
||||
*/
|
||||
public void copyContent(TagField field) {
|
||||
if (field instanceof TagTextField) {
|
||||
this.content = ((TagTextField) field).getContent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see org.jaudiotagger.tag.TagTextField#getContent()
|
||||
*/
|
||||
public String getContent() {
|
||||
return this.content;
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see org.jaudiotagger.tag.TagTextField#setContent(java.lang.String)
|
||||
*/
|
||||
public void setContent(String s) {
|
||||
this.content = s;
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see org.jaudiotagger.tag.TagTextField#getEncoding()
|
||||
*/
|
||||
public String getEncoding() {
|
||||
return "ISO-8859-1";
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see org.jaudiotagger.tag.TagTextField#setEncoding(java.lang.String)
|
||||
*/
|
||||
public void setEncoding(String s) {
|
||||
/* Not allowed */
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see org.jaudiotagger.tag.TagField#getId()
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see org.jaudiotagger.tag.TagField#getRawContent()
|
||||
*/
|
||||
public byte[] getRawContent() {
|
||||
return this.content == null ? new byte[]{} : Utils.getDefaultBytes(this.content, getEncoding());
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see org.jaudiotagger.tag.TagField#isBinary()
|
||||
*/
|
||||
public boolean isBinary() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see org.jaudiotagger.tag.TagField#isBinary(boolean)
|
||||
*/
|
||||
public void isBinary(boolean b) {
|
||||
/* not supported */
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see org.jaudiotagger.tag.TagField#isCommon()
|
||||
*/
|
||||
public boolean isCommon() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see org.jaudiotagger.tag.TagField#isEmpty()
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return this.content.equals("");
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
public String toString() {
|
||||
return getContent();
|
||||
}
|
||||
} // End of AiffTagTextField
|
||||
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
/**
|
||||
* Enum for AIFF fields that don't have obvious matches in FieldKey
|
||||
*/
|
||||
public enum AiffTagFieldKey {
|
||||
TIMESTAMP("TIMESTAMP");
|
||||
|
||||
private String fieldName;
|
||||
|
||||
AiffTagFieldKey(String fieldName) {
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
//import java.io.EOFException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
//import java.io.InputStream;
|
||||
|
||||
public class AiffUtil {
|
||||
|
||||
private final static SimpleDateFormat dateFmt =
|
||||
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
|
||||
|
||||
private final static Charset LATIN1 = Charset.availableCharsets().get("ISO-8859-1");
|
||||
|
||||
/**
|
||||
* Reads 4 bytes from file and interprets them as UINT32.<br>
|
||||
*
|
||||
* @param raf file to read from.
|
||||
* @return UINT32 value
|
||||
* @throws IOException on I/O Errors.
|
||||
*/
|
||||
public static long readUINT32(RandomAccessFile raf) throws IOException {
|
||||
long result = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
// Warning, always cast to long here. Otherwise it will be
|
||||
// shifted as int, which may produce a negative value, which will
|
||||
// then be extended to long and assign the long variable a negative
|
||||
// value.
|
||||
result <<= 8;
|
||||
result |= (long) raf.read();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads 4 bytes and concatenates them into a String.
|
||||
* This pattern is used for ID's of various kinds.
|
||||
*/
|
||||
public static String read4Chars(RandomAccessFile raf) throws IOException {
|
||||
StringBuffer sbuf = new StringBuffer(4);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
char ch = (char) raf.read();
|
||||
sbuf.append(ch);
|
||||
}
|
||||
return sbuf.toString();
|
||||
}
|
||||
|
||||
public static double read80BitDouble(RandomAccessFile raf)
|
||||
throws IOException {
|
||||
byte[] buf = new byte[10];
|
||||
raf.readFully(buf);
|
||||
ExtDouble xd = new ExtDouble(buf);
|
||||
return xd.toDouble();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Macintosh-style timestamp (seconds since
|
||||
* January 1, 1904) into a Java date. The timestamp is
|
||||
* treated as a time in the default localization.
|
||||
* Depending on that localization,
|
||||
* there may be some variation in the exact hour of the date
|
||||
* returned, e.g., due to daylight savings time.
|
||||
*/
|
||||
public static Date timestampToDate(long timestamp) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.set(1904, 0, 1, 0, 0, 0);
|
||||
|
||||
// If we add the seconds directly, we'll truncate the long
|
||||
// value when converting to int. So convert to hours plus
|
||||
// residual seconds.
|
||||
int hours = (int) (timestamp / 3600);
|
||||
int seconds = (int) (timestamp - (long) hours * 3600L);
|
||||
cal.add(Calendar.HOUR_OF_DAY, hours);
|
||||
cal.add(Calendar.SECOND, seconds);
|
||||
Date dat = cal.getTime();
|
||||
return dat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date as text
|
||||
*/
|
||||
public static String formatDate(Date dat) {
|
||||
return dateFmt.format(dat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a byte array to a Pascal string. The first byte is the byte count,
|
||||
* followed by that many active characters.
|
||||
*/
|
||||
public static String bytesToPascalString(byte[] data) {
|
||||
int len = (int) data[0];
|
||||
return new String(data, 0, len, LATIN1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a Pascal string from the file.
|
||||
*/
|
||||
public static String readPascalString(RandomAccessFile raf) throws IOException {
|
||||
int len = raf.read();
|
||||
byte[] buf = new byte[len + 1];
|
||||
raf.read(buf, 1, len);
|
||||
buf[0] = (byte) len;
|
||||
return bytesToPascalString(buf);
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
public class AnnotationChunk extends TextChunk {
|
||||
|
||||
private AiffAudioHeader aiffHeader;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param hdr The header for this chunk
|
||||
* @param raf The file from which the AIFF data are being read
|
||||
* @param aHdr The AiffAudioHeader into which information is stored
|
||||
*/
|
||||
public AnnotationChunk(
|
||||
ChunkHeader hdr,
|
||||
RandomAccessFile raf,
|
||||
AiffAudioHeader aHdr) {
|
||||
super(hdr, raf);
|
||||
aiffHeader = aHdr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readChunk() throws IOException {
|
||||
if (!super.readChunk()) {
|
||||
return false;
|
||||
}
|
||||
aiffHeader.addAnnotation(chunkText);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import org.jaudiotagger.audio.generic.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
public class ApplicationChunk extends Chunk {
|
||||
|
||||
// private AiffTag aiffTag;
|
||||
private AiffAudioHeader aiffHeader;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param hdr The header for this chunk
|
||||
* @param raf The file from which the AIFF data are being read
|
||||
* @param tag The AiffTag into which information is stored
|
||||
*/
|
||||
public ApplicationChunk(
|
||||
ChunkHeader hdr,
|
||||
RandomAccessFile raf,
|
||||
AiffAudioHeader aHdr) {
|
||||
super(raf, hdr);
|
||||
aiffHeader = aHdr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a chunk and puts an Application property into
|
||||
* the RepInfo object.
|
||||
*
|
||||
* @return <code>false</code> if the chunk is structurally
|
||||
* invalid, otherwise <code>true</code>
|
||||
*/
|
||||
public boolean readChunk() throws IOException {
|
||||
String applicationSignature = Utils.readString(raf, 4);
|
||||
String applicationName = null;
|
||||
byte[] data = new byte[(int) (bytesLeft - 4)];
|
||||
raf.readFully(data);
|
||||
// If the application signature is 'pdos' or 'stoc',
|
||||
// then the beginning of the data area is a Pascal
|
||||
// string naming the application. Otherwise, we
|
||||
// ignore the data. ('pdos' is for Apple II
|
||||
// applications, 'stoc' for the entire non-Apple world.)
|
||||
if ("stoc".equals(applicationSignature) ||
|
||||
"pdos".equals(applicationSignature)) {
|
||||
applicationName = AiffUtil.bytesToPascalString(data);
|
||||
}
|
||||
aiffHeader.addApplicationIdentifier
|
||||
(applicationSignature + ": " + applicationName);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
public class AuthorChunk extends TextChunk {
|
||||
|
||||
private AiffAudioHeader aiffHeader;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param hdr The header for this chunk
|
||||
* @param raf The file from which the AIFF data are being read
|
||||
* @param aHdr The AiffAudioHeader into which information is stored
|
||||
*/
|
||||
public AuthorChunk(
|
||||
ChunkHeader hdr,
|
||||
RandomAccessFile raf,
|
||||
AiffAudioHeader aHdr) {
|
||||
super(hdr, raf);
|
||||
aiffHeader = aHdr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readChunk() throws IOException {
|
||||
if (!super.readChunk()) {
|
||||
return false;
|
||||
}
|
||||
aiffHeader.setAuthor(chunkText);
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
|
||||
/**
|
||||
* Abstract superclass for IFF/AIFF chunks.
|
||||
*
|
||||
* @author Gary McGath
|
||||
*/
|
||||
public abstract class Chunk {
|
||||
|
||||
protected long bytesLeft;
|
||||
protected RandomAccessFile raf;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param module The Module under which this was called
|
||||
* @param hdr The header for this chunk
|
||||
* @param dstrm The stream from which the data are being read
|
||||
*/
|
||||
public Chunk(RandomAccessFile raf, ChunkHeader hdr) {
|
||||
this.raf = raf;
|
||||
bytesLeft = hdr.getSize();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads a chunk and puts appropriate information into
|
||||
* the RepInfo object.
|
||||
*
|
||||
* @param info RepInfo object to receive information
|
||||
* @return <code>false</code> if the chunk is structurally
|
||||
* invalid, otherwise <code>true</code>
|
||||
*/
|
||||
public abstract boolean readChunk() throws IOException;
|
||||
|
||||
/**
|
||||
* Convert a byte buffer cleanly to an ASCII string.
|
||||
* This is used for fixed-allocation strings in Broadcast
|
||||
* WAVE chunks, and might have uses elsewhere.
|
||||
* If a string is shorter than its fixed allocation, we're
|
||||
* guaranteed only that there is a null terminating the string,
|
||||
* and noise could follow it. So we can't use the byte buffer
|
||||
* constructor for a string.
|
||||
*/
|
||||
protected String byteBufString(byte[] b) {
|
||||
StringBuffer sb = new StringBuffer(b.length);
|
||||
for (int i = 0; i < b.length; i++) {
|
||||
byte c = b[i];
|
||||
if (c == 0) {
|
||||
// Terminate when we see a null
|
||||
break;
|
||||
}
|
||||
sb.append((char) c);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
public class ChunkHeader {
|
||||
|
||||
private long _size; // This does not include the 8 bytes of header
|
||||
private String _chunkID; // 4-character ID of the chunk
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param module The module under which the chunk is being read
|
||||
* @param info The RepInfo object being used by the module
|
||||
*/
|
||||
public ChunkHeader() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads the header of a chunk. If _chunkID is non-null,
|
||||
* it's assumed to have already been read.
|
||||
*/
|
||||
public boolean readHeader(RandomAccessFile raf) throws IOException {
|
||||
StringBuffer id = new StringBuffer(4);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int ch = raf.read();
|
||||
if (ch < 32) {
|
||||
String hx = Integer.toHexString(ch);
|
||||
if (hx.length() < 2) {
|
||||
hx = "0" + hx;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
id.append((char) ch);
|
||||
}
|
||||
_chunkID = id.toString();
|
||||
_size = AiffUtil.readUINT32(raf);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the chunk type, which is a 4-character code
|
||||
*/
|
||||
public String getID() {
|
||||
return _chunkID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the chunk type, which is a 4-character code, directly.
|
||||
*/
|
||||
public void setID(String id) {
|
||||
_chunkID = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the chunk size (excluding the first 8 bytes)
|
||||
*/
|
||||
public long getSize() {
|
||||
return _size;
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import org.jaudiotagger.audio.generic.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.Date;
|
||||
|
||||
public class CommentsChunk extends Chunk {
|
||||
|
||||
private AiffAudioHeader aiffHeader;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param hdr The header for this chunk
|
||||
* @param raf The file from which the AIFF data are being read
|
||||
* @param tag The AiffTag into which information is stored
|
||||
*/
|
||||
public CommentsChunk(
|
||||
ChunkHeader hdr,
|
||||
RandomAccessFile raf,
|
||||
AiffAudioHeader aHdr) {
|
||||
super(raf, hdr);
|
||||
aiffHeader = aHdr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a chunk and extracts information.
|
||||
*
|
||||
* @return <code>false</code> if the chunk is structurally
|
||||
* invalid, otherwise <code>true</code>
|
||||
*/
|
||||
public boolean readChunk() throws IOException {
|
||||
int numComments = Utils.readUint16(raf);
|
||||
// Create a List of comments
|
||||
for (int i = 0; i < numComments; i++) {
|
||||
long timestamp = Utils.readUint32(raf);
|
||||
Date jTimestamp = AiffUtil.timestampToDate(timestamp);
|
||||
int marker = Utils.readInt16(raf);
|
||||
int count = Utils.readUint16(raf);
|
||||
bytesLeft -= 8;
|
||||
byte[] buf = new byte[count];
|
||||
raf.read(buf);
|
||||
bytesLeft -= count;
|
||||
String cmt = new String(buf);
|
||||
|
||||
// Append a timestamp to the comment
|
||||
cmt += " " + AiffUtil.formatDate(jTimestamp);
|
||||
aiffHeader.addComment(cmt);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import org.jaudiotagger.audio.generic.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
public class CommonChunk extends Chunk {
|
||||
|
||||
private AiffAudioHeader aiffHeader;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param hdr The header for this chunk
|
||||
* @param raf The file from which the AIFF data are being read
|
||||
* @param tag The AiffTag into which information is stored
|
||||
*/
|
||||
public CommonChunk(
|
||||
ChunkHeader hdr,
|
||||
RandomAccessFile raf,
|
||||
AiffAudioHeader aHdr) {
|
||||
super(raf, hdr);
|
||||
aiffHeader = aHdr;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean readChunk() throws IOException {
|
||||
int numChannels = Utils.readUint16(raf);
|
||||
long numSampleFrames = Utils.readUint32(raf);
|
||||
int sampleSize = Utils.readUint16(raf);
|
||||
bytesLeft -= 8;
|
||||
|
||||
String compressionType = null;
|
||||
String compressionName = null;
|
||||
|
||||
double sampleRate = AiffUtil.read80BitDouble(raf);
|
||||
bytesLeft -= 10;
|
||||
|
||||
if (aiffHeader.getFileType() == AiffAudioHeader.FileType.AIFCTYPE) {
|
||||
if (bytesLeft == 0) {
|
||||
// This is a rather special case, but testing did turn up
|
||||
// a file that misbehaved in this way.
|
||||
return false;
|
||||
}
|
||||
compressionType = AiffUtil.read4Chars(raf);
|
||||
// According to David Ackerman, the compression type can
|
||||
// change the endianness of the document.
|
||||
if (compressionType.equals("sowt")) {
|
||||
aiffHeader.setEndian(AiffAudioHeader.Endian.LITTLE_ENDIAN);
|
||||
}
|
||||
bytesLeft -= 4;
|
||||
compressionName = AiffUtil.readPascalString(raf);
|
||||
bytesLeft -= compressionName.length() + 1;
|
||||
}
|
||||
|
||||
aiffHeader.setBitsPerSample(sampleSize);
|
||||
aiffHeader.setSamplingRate((int) sampleRate);
|
||||
aiffHeader.setChannelNumber(numChannels);
|
||||
aiffHeader.setLength((int) (numSampleFrames / sampleRate));
|
||||
aiffHeader.setPreciseLength((float) (numSampleFrames / sampleRate));
|
||||
aiffHeader.setLossless(true); // for all known compression types
|
||||
// Proper handling of compression type should depend
|
||||
// on whether raw output is set
|
||||
if (compressionType != null) {
|
||||
if (compressionType.equals("NONE")) {
|
||||
} else if (compressionType.equals("raw ")) {
|
||||
compressionName = "PCM 8-bit offset-binary";
|
||||
} else if (compressionType.equals("twos")) {
|
||||
compressionName = "PCM 16-bit twos-complement big-endian";
|
||||
} else if (compressionType.equals("sowt")) {
|
||||
compressionName = "PCM 16-bit twos-complement little-endian";
|
||||
} else if (compressionType.equals("fl32")) {
|
||||
compressionName = "PCM 32-bit integer";
|
||||
} else if (compressionType.equals("fl64")) {
|
||||
compressionName = "PCM 64-bit floating point";
|
||||
} else if (compressionType.equals("in24")) {
|
||||
compressionName = "PCM 24-bit integer";
|
||||
} else if (compressionType.equals("in32")) {
|
||||
compressionName = "PCM 32-bit integer";
|
||||
} else {
|
||||
aiffHeader.setLossless(false); // We don't know, so we have to assume lossy
|
||||
}
|
||||
aiffHeader.setAudioEncoding(compressionName);
|
||||
|
||||
// The size of the data after compression isn't available
|
||||
// from the Common chunk, so we mark it as "unknown."
|
||||
// With a bit more sophistication, we could combine the
|
||||
// information from here and the Sound Data chunk to get
|
||||
// the effective byte rate, but we're about to release.
|
||||
String name = compressionName;
|
||||
if (name == null || name.length() == 0) {
|
||||
name = compressionType;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
public class CopyrightChunk extends TextChunk {
|
||||
|
||||
private AiffAudioHeader aiffHeader;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param hdr The header for this chunk
|
||||
* @param raf The file from which the AIFF data are being read
|
||||
* @param aHdr The AiffAudioHeader into which information is stored
|
||||
*/
|
||||
public CopyrightChunk(
|
||||
ChunkHeader hdr,
|
||||
RandomAccessFile raf,
|
||||
AiffAudioHeader aHdr) {
|
||||
super(hdr, raf);
|
||||
aiffHeader = aHdr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readChunk() throws IOException {
|
||||
if (!super.readChunk()) {
|
||||
return false;
|
||||
}
|
||||
aiffHeader.setCopyright(chunkText);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
/**
|
||||
* Code to deal with the 80-bit floating point (extended double)
|
||||
* numbers which occur in AIFF files. Should also be applicable
|
||||
* in general.
|
||||
* <p>
|
||||
* Java has no built-in support for IEEE 754 extended double numbers.
|
||||
* Thus, we have to unpack the number and convert it to a double by
|
||||
* hand. There is, of course, loss of precision.
|
||||
* <p>
|
||||
* This isn't designed for high-precision work; as the standard
|
||||
* disclaimer says, don't use it for life support systems or nuclear
|
||||
* power plants.
|
||||
* <p>
|
||||
* Lifted bodily from JHOVE.
|
||||
*
|
||||
* @author Gary McGath
|
||||
*/
|
||||
public class ExtDouble {
|
||||
|
||||
byte[] _rawData;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param rawData A 10-byte array representing the number
|
||||
* in the sequence in which it was stored.
|
||||
*/
|
||||
public ExtDouble(byte[] rawData) {
|
||||
_rawData = rawData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert the value to a Java double. This results in
|
||||
* loss of precision. If the number is out of range,
|
||||
* results aren't guaranteed.
|
||||
*/
|
||||
public double toDouble() {
|
||||
int sign;
|
||||
int exponent;
|
||||
long mantissa = 0;
|
||||
|
||||
// Extract the sign bit.
|
||||
sign = _rawData[0] >> 7;
|
||||
|
||||
// Extract the exponent. It's stored with a
|
||||
// bias of 16383, so subtract that off.
|
||||
// Also, the mantissa is between 1 and 2 (i.e.,
|
||||
// all but 1 digits are to the right of the binary point, so
|
||||
// we take 62 (not 63: see below) off the exponent for that.
|
||||
exponent = (_rawData[0] << 8) | _rawData[1];
|
||||
exponent &= 0X7FFF; // strip off sign bit
|
||||
exponent -= (16383 + 62); // 1 is added to the "real" exponent
|
||||
|
||||
// Extract the mantissa. It's 64 bits of unsigned
|
||||
// data, but a long is a signed number, so we have to
|
||||
// discard the LSB. We'll lose more than that converting
|
||||
// to double anyway. This division by 2 is the reason for
|
||||
// adding an extra 1 to the exponent above.
|
||||
int shifter = 55;
|
||||
for (int i = 2; i < 9; i++) {
|
||||
mantissa |= ((long) _rawData[i] & 0XFFL) << shifter;
|
||||
shifter -= 8;
|
||||
}
|
||||
mantissa |= _rawData[9] >>> 1;
|
||||
|
||||
// Now put it together in a floating point number.
|
||||
double val = Math.pow(2, exponent);
|
||||
val *= mantissa;
|
||||
if (sign != 0) {
|
||||
val = -val;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import org.jaudiotagger.audio.generic.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.Date;
|
||||
|
||||
public class FormatVersionChunk extends Chunk {
|
||||
|
||||
private AiffAudioHeader aiffHeader;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param hdr The header for this chunk
|
||||
* @param raf The file from which the AIFF data are being read
|
||||
* @param tag The AiffTag into which information is stored
|
||||
*/
|
||||
public FormatVersionChunk(
|
||||
ChunkHeader hdr,
|
||||
RandomAccessFile raf,
|
||||
AiffAudioHeader aHdr) {
|
||||
super(raf, hdr);
|
||||
aiffHeader = aHdr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a chunk and extracts information.
|
||||
*
|
||||
* @return <code>false</code> if the chunk is structurally
|
||||
* invalid, otherwise <code>true</code>
|
||||
*/
|
||||
public boolean readChunk() throws IOException {
|
||||
long rawTimestamp = Utils.readUint32(raf);
|
||||
// The timestamp is in seconds since January 1, 1904.
|
||||
// We must convert to Java time.
|
||||
Date timestamp = AiffUtil.timestampToDate(rawTimestamp);
|
||||
aiffHeader.setTimestamp(timestamp);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import org.jaudiotagger.audio.AudioFile;
|
||||
import org.jaudiotagger.audio.exceptions.CannotReadException;
|
||||
import org.jaudiotagger.tag.TagException;
|
||||
import org.jaudiotagger.tag.aiff.AiffTag;
|
||||
import org.jaudiotagger.tag.id3.AbstractID3v2Tag;
|
||||
import org.jaudiotagger.tag.id3.ID3v22Tag;
|
||||
import org.jaudiotagger.tag.id3.ID3v23Tag;
|
||||
import org.jaudiotagger.tag.id3.ID3v24Tag;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ID3Chunk extends Chunk {
|
||||
|
||||
private AiffTag aiffTag;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param hdr The header for this chunk
|
||||
* @param raf The file from which the AIFF data are being read
|
||||
* @param tag The AiffTag into which information is stored
|
||||
*/
|
||||
public ID3Chunk(
|
||||
ChunkHeader hdr,
|
||||
RandomAccessFile raf,
|
||||
AiffTag tag) {
|
||||
super(raf, hdr);
|
||||
aiffTag = tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readChunk() throws IOException {
|
||||
// TODO Auto-generated method stub
|
||||
if (!isId3v2Tag()) {
|
||||
return false; // Bad ID3V2 tag
|
||||
}
|
||||
int version = raf.read();
|
||||
AbstractID3v2Tag id3Tag;
|
||||
switch (version) {
|
||||
case 2:
|
||||
id3Tag = new ID3v22Tag();
|
||||
AudioFile.logger.finest("Reading ID3V2.2 tag");
|
||||
break;
|
||||
case 3:
|
||||
id3Tag = new ID3v23Tag();
|
||||
AudioFile.logger.finest("Reading ID3V2.3 tag");
|
||||
break;
|
||||
case 4:
|
||||
id3Tag = new ID3v24Tag();
|
||||
AudioFile.logger.finest("Reading ID3V2.4 tag");
|
||||
break;
|
||||
default:
|
||||
return false; // bad or unknown version
|
||||
}
|
||||
aiffTag.setID3Tag(id3Tag);
|
||||
raf.seek(raf.getFilePointer() - 4); // back up to start of tag
|
||||
byte[] buf = new byte[(int) bytesLeft];
|
||||
raf.read(buf);
|
||||
ByteBuffer bb = ByteBuffer.allocate((int) bytesLeft);
|
||||
bb.put(buf);
|
||||
try {
|
||||
id3Tag.read(bb);
|
||||
} catch (TagException e) {
|
||||
AudioFile.logger.info("Exception reading ID3 tag: " + e.getClass().getName()
|
||||
+ ": " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param rawdata
|
||||
* @param isFramingBit
|
||||
* @return logical representation of VorbisCommentTag
|
||||
* @throws IOException
|
||||
* @throws CannotReadException
|
||||
*/
|
||||
public void parse(byte[] rawdata, AiffTag aiffTag) throws IOException, CannotReadException {
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads 3 bytes to determine if the tag really looks like ID3 data.
|
||||
*/
|
||||
private boolean isId3v2Tag() throws IOException {
|
||||
byte buf[] = new byte[3];
|
||||
raf.read(buf);
|
||||
String id = new String(buf, "ASCII");
|
||||
return "ID3".equals(id);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
public class NameChunk extends TextChunk {
|
||||
|
||||
private AiffAudioHeader aiffHeader;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param hdr The header for this chunk
|
||||
* @param raf The file from which the AIFF data are being read
|
||||
* @param aHdr The AiffAudioHeader into which information is stored
|
||||
*/
|
||||
public NameChunk(
|
||||
ChunkHeader hdr,
|
||||
RandomAccessFile raf,
|
||||
AiffAudioHeader aHdr) {
|
||||
super(hdr, raf);
|
||||
aiffHeader = aHdr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readChunk() throws IOException {
|
||||
if (!super.readChunk()) {
|
||||
return false;
|
||||
}
|
||||
aiffHeader.setName(chunkText);
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package org.jaudiotagger.audio.aiff;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
/**
|
||||
* This class provides common functionality for NameChunk, AuthorChunk,
|
||||
* and CopyrightChunk
|
||||
*/
|
||||
public abstract class TextChunk extends Chunk {
|
||||
|
||||
protected String chunkText;
|
||||
private AiffAudioHeader aiffHeader;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param hdr The header for this chunk
|
||||
* @param raf The file from which the AIFF data are being read
|
||||
*/
|
||||
public TextChunk(
|
||||
ChunkHeader hdr,
|
||||
RandomAccessFile raf) {
|
||||
super(raf, hdr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the chunk. The subclasses need to take the value of
|
||||
* chunkText and use it appropriately.
|
||||
*/
|
||||
@Override
|
||||
public boolean readChunk() throws IOException {
|
||||
|
||||
byte[] buf = new byte[(int) bytesLeft];
|
||||
raf.read(buf);
|
||||
chunkText = new String(buf, "ISO-8859-1");
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
|
||||
|
||||
<html long="en">
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
<b>Very preliminary</b> code for AIFF files.
|
||||
<b>Not even remotely working yet.</b> Uploaded for review.
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
<!-- package.html by Gary McGath -->
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,267 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf;
|
||||
|
||||
import org.jaudiotagger.audio.AudioFile;
|
||||
import org.jaudiotagger.audio.asf.data.AsfHeader;
|
||||
import org.jaudiotagger.audio.asf.data.AudioStreamChunk;
|
||||
import org.jaudiotagger.audio.asf.data.MetadataContainer;
|
||||
import org.jaudiotagger.audio.asf.data.MetadataDescriptor;
|
||||
import org.jaudiotagger.audio.asf.io.*;
|
||||
import org.jaudiotagger.audio.asf.util.TagConverter;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
import org.jaudiotagger.audio.exceptions.CannotReadException;
|
||||
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
|
||||
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
|
||||
import org.jaudiotagger.audio.generic.AudioFileReader;
|
||||
import org.jaudiotagger.audio.generic.GenericAudioHeader;
|
||||
import org.jaudiotagger.logging.ErrorMessage;
|
||||
import org.jaudiotagger.tag.TagException;
|
||||
import org.jaudiotagger.tag.asf.AsfTag;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* This reader can read ASF files containing any content (stream type). <br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class AsfFileReader extends AudioFileReader {
|
||||
|
||||
/**
|
||||
* Logger instance
|
||||
*/
|
||||
private final static Logger LOGGER = Logger
|
||||
.getLogger("org.jaudiotagger.audio.asf");
|
||||
|
||||
/**
|
||||
* This reader will be configured to read tag and audio header information.<br>
|
||||
*/
|
||||
private final static AsfHeaderReader HEADER_READER;
|
||||
|
||||
static {
|
||||
final List<Class<? extends ChunkReader>> readers = new ArrayList<Class<? extends ChunkReader>>();
|
||||
readers.add(ContentDescriptionReader.class);
|
||||
readers.add(ContentBrandingReader.class);
|
||||
readers.add(MetadataReader.class);
|
||||
readers.add(LanguageListReader.class);
|
||||
|
||||
// Create the header extension object reader with just content
|
||||
// description reader as well
|
||||
// as extended content description reader.
|
||||
final AsfExtHeaderReader extReader = new AsfExtHeaderReader(readers,
|
||||
true);
|
||||
readers.add(FileHeaderReader.class);
|
||||
readers.add(StreamChunkReader.class);
|
||||
HEADER_READER = new AsfHeaderReader(readers, true);
|
||||
HEADER_READER.setExtendedHeaderReader(extReader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the "isVbr" field is set in the extended content
|
||||
* description.<br>
|
||||
*
|
||||
* @param header the header to look up.
|
||||
* @return <code>true</code> if "isVbr" is present with a
|
||||
* <code>true</code> value.
|
||||
*/
|
||||
private boolean determineVariableBitrate(final AsfHeader header) {
|
||||
assert header != null;
|
||||
boolean result = false;
|
||||
final MetadataContainer extDesc = header
|
||||
.findExtendedContentDescription();
|
||||
if (extDesc != null) {
|
||||
final List<MetadataDescriptor> descriptors = extDesc
|
||||
.getDescriptorsByName("IsVBR");
|
||||
if (descriptors != null && !descriptors.isEmpty()) {
|
||||
result = Boolean.TRUE.toString().equals(
|
||||
descriptors.get(0).getString());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a generic audio header instance with provided data from header.
|
||||
*
|
||||
* @param header ASF header which contains the information.
|
||||
* @return generic audio header representation.
|
||||
* @throws CannotReadException If header does not contain mandatory information. (Audio
|
||||
* stream chunk and file header chunk)
|
||||
*/
|
||||
private GenericAudioHeader getAudioHeader(final AsfHeader header)
|
||||
throws CannotReadException {
|
||||
final GenericAudioHeader info = new GenericAudioHeader();
|
||||
if (header.getFileHeader() == null) {
|
||||
throw new CannotReadException(
|
||||
"Invalid ASF/WMA file. File header object not available.");
|
||||
}
|
||||
if (header.getAudioStreamChunk() == null) {
|
||||
throw new CannotReadException(
|
||||
"Invalid ASF/WMA file. No audio stream contained.");
|
||||
}
|
||||
info.setBitrate(header.getAudioStreamChunk().getKbps());
|
||||
info.setChannelNumber((int) header.getAudioStreamChunk()
|
||||
.getChannelCount());
|
||||
info.setEncodingType("ASF (audio): "
|
||||
+ header.getAudioStreamChunk().getCodecDescription());
|
||||
info
|
||||
.setLossless(header.getAudioStreamChunk()
|
||||
.getCompressionFormat() == AudioStreamChunk.WMA_LOSSLESS);
|
||||
info.setPreciseLength(header.getFileHeader().getPreciseDuration());
|
||||
info.setSamplingRate((int) header.getAudioStreamChunk()
|
||||
.getSamplingRate());
|
||||
info.setVariableBitRate(determineVariableBitrate(header));
|
||||
info.setBitsPerSample(header.getAudioStreamChunk().getBitsPerSample());
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see org.jaudiotagger.audio.generic.AudioFileReader#getEncodingInfo(java.io.RandomAccessFile)
|
||||
*/
|
||||
@Override
|
||||
protected GenericAudioHeader getEncodingInfo(final RandomAccessFile raf)
|
||||
throws CannotReadException, IOException {
|
||||
raf.seek(0);
|
||||
GenericAudioHeader info;
|
||||
try {
|
||||
final AsfHeader header = AsfHeaderReader.readInfoHeader(raf);
|
||||
if (header == null) {
|
||||
throw new CannotReadException(
|
||||
"Some values must have been "
|
||||
+ "incorrect for interpretation as asf with wma content.");
|
||||
}
|
||||
info = getAudioHeader(header);
|
||||
} catch (final Exception e) {
|
||||
if (e instanceof IOException) {
|
||||
throw (IOException) e;
|
||||
} else if (e instanceof CannotReadException) {
|
||||
throw (CannotReadException) e;
|
||||
} else {
|
||||
throw new CannotReadException("Failed to read. Cause: "
|
||||
+ e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tag instance with provided data from header.
|
||||
*
|
||||
* @param header ASF header which contains the information.
|
||||
* @return generic audio header representation.
|
||||
*/
|
||||
private AsfTag getTag(final AsfHeader header) {
|
||||
return TagConverter.createTagOf(header);
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see org.jaudiotagger.audio.generic.AudioFileReader#getTag(java.io.RandomAccessFile)
|
||||
*/
|
||||
@Override
|
||||
protected AsfTag getTag(final RandomAccessFile raf)
|
||||
throws CannotReadException, IOException {
|
||||
raf.seek(0);
|
||||
AsfTag tag;
|
||||
try {
|
||||
final AsfHeader header = AsfHeaderReader.readTagHeader(raf);
|
||||
if (header == null) {
|
||||
throw new CannotReadException(
|
||||
"Some values must have been "
|
||||
+ "incorrect for interpretation as asf with wma content.");
|
||||
}
|
||||
|
||||
tag = TagConverter.createTagOf(header);
|
||||
|
||||
} catch (final Exception e) {
|
||||
logger.severe(e.getMessage());
|
||||
if (e instanceof IOException) {
|
||||
throw (IOException) e;
|
||||
} else if (e instanceof CannotReadException) {
|
||||
throw (CannotReadException) e;
|
||||
} else {
|
||||
throw new CannotReadException("Failed to read. Cause: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public AudioFile read(final File f) throws CannotReadException,
|
||||
IOException, TagException, ReadOnlyFileException,
|
||||
InvalidAudioFrameException {
|
||||
if (!f.canRead()) {
|
||||
throw new CannotReadException(
|
||||
ErrorMessage.GENERAL_READ_FAILED_DO_NOT_HAVE_PERMISSION_TO_READ_FILE
|
||||
.getMsg(f.getAbsolutePath()));
|
||||
}
|
||||
InputStream stream = null;
|
||||
try {
|
||||
stream = new FullRequestInputStream(new BufferedInputStream(
|
||||
new FileInputStream(f)));
|
||||
final AsfHeader header = HEADER_READER.read(Utils.readGUID(stream),
|
||||
stream, 0);
|
||||
if (header == null) {
|
||||
throw new CannotReadException(ErrorMessage.ASF_HEADER_MISSING
|
||||
.getMsg(f.getAbsolutePath()));
|
||||
}
|
||||
if (header.getFileHeader() == null) {
|
||||
throw new CannotReadException(
|
||||
ErrorMessage.ASF_FILE_HEADER_MISSING.getMsg(f
|
||||
.getAbsolutePath()));
|
||||
}
|
||||
|
||||
// Just log a warning because file seems to play okay
|
||||
if (header.getFileHeader().getFileSize().longValue() != f.length()) {
|
||||
logger
|
||||
.warning(ErrorMessage.ASF_FILE_HEADER_SIZE_DOES_NOT_MATCH_FILE_SIZE
|
||||
.getMsg(f.getAbsolutePath(), header
|
||||
.getFileHeader().getFileSize()
|
||||
.longValue(), f.length()));
|
||||
}
|
||||
|
||||
return new AudioFile(f, getAudioHeader(header), getTag(header));
|
||||
|
||||
} catch (final CannotReadException e) {
|
||||
throw e;
|
||||
} catch (final Exception e) {
|
||||
throw new CannotReadException("\"" + f + "\" :" + e, e);
|
||||
} finally {
|
||||
try {
|
||||
if (stream != null) {
|
||||
stream.close();
|
||||
}
|
||||
} catch (final Exception ex) {
|
||||
LOGGER.severe("\"" + f + "\" :" + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.AsfHeader;
|
||||
import org.jaudiotagger.audio.asf.data.ChunkContainer;
|
||||
import org.jaudiotagger.audio.asf.data.MetadataContainer;
|
||||
import org.jaudiotagger.audio.asf.io.*;
|
||||
import org.jaudiotagger.audio.asf.util.TagConverter;
|
||||
import org.jaudiotagger.audio.exceptions.CannotWriteException;
|
||||
import org.jaudiotagger.audio.generic.AudioFileWriter;
|
||||
import org.jaudiotagger.tag.Tag;
|
||||
import org.jaudiotagger.tag.asf.AsfTag;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class writes given tags to ASF files containing WMA content. <br>
|
||||
* <br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class AsfFileWriter extends AudioFileWriter {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void deleteTag(final RandomAccessFile raf,
|
||||
final RandomAccessFile tempRaf) throws CannotWriteException,
|
||||
IOException {
|
||||
writeTag(new AsfTag(true), raf, tempRaf);
|
||||
}
|
||||
|
||||
private boolean[] searchExistence(final ChunkContainer container,
|
||||
final MetadataContainer[] metaContainers) {
|
||||
assert container != null;
|
||||
assert metaContainers != null;
|
||||
final boolean[] result = new boolean[metaContainers.length];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = container.hasChunkByGUID(metaContainers[i]
|
||||
.getContainerType().getContainerGUID());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void writeTag(final Tag tag, final RandomAccessFile raf,
|
||||
final RandomAccessFile rafTemp) throws CannotWriteException,
|
||||
IOException {
|
||||
/*
|
||||
* Since this implementation should not change the structure of the ASF
|
||||
* file (locations of content description chunks), we need to read the
|
||||
* content description chunk and the extended content description chunk
|
||||
* from the source file. In the second step we need to determine which
|
||||
* modifier (asf header or asf extended header) gets the appropriate
|
||||
* modifiers. The following policies are applied: if the source does not
|
||||
* contain any descriptor, the necessary descriptors are appended to the
|
||||
* header object.
|
||||
*
|
||||
* if the source contains only one descriptor in the header extension
|
||||
* object, and the other type is needed as well, the other one will be
|
||||
* put into the header extension object.
|
||||
*
|
||||
* for each descriptor type, if an object is found, an updater will be
|
||||
* configured.
|
||||
*/
|
||||
final AsfHeader sourceHeader = AsfHeaderReader.readTagHeader(raf);
|
||||
raf.seek(0); // Reset for the streamer
|
||||
/*
|
||||
* Now createField modifiers for metadata descriptor and extended content
|
||||
* descriptor as implied by the given Tag.
|
||||
*/
|
||||
// TODO not convinced that we need to copy fields here
|
||||
final AsfTag copy = new AsfTag(tag, true);
|
||||
final MetadataContainer[] distribution = TagConverter
|
||||
.distributeMetadata(copy);
|
||||
final boolean[] existHeader = searchExistence(sourceHeader,
|
||||
distribution);
|
||||
final boolean[] existExtHeader = searchExistence(sourceHeader
|
||||
.getExtendedHeader(), distribution);
|
||||
// Modifiers for the asf header object
|
||||
final List<ChunkModifier> headerModifier = new ArrayList<ChunkModifier>();
|
||||
// Modifiers for the asf header extension object
|
||||
final List<ChunkModifier> extHeaderModifier = new ArrayList<ChunkModifier>();
|
||||
for (int i = 0; i < distribution.length; i++) {
|
||||
final WriteableChunkModifer modifier = new WriteableChunkModifer(
|
||||
distribution[i]);
|
||||
if (existHeader[i]) {
|
||||
// Will remove or modify chunks in ASF header
|
||||
headerModifier.add(modifier);
|
||||
} else if (existExtHeader[i]) {
|
||||
// Will remove or modify chunks in extended header
|
||||
extHeaderModifier.add(modifier);
|
||||
} else {
|
||||
// Objects (chunks) will be added here.
|
||||
if (i == 0 || i == 2 || i == 1) {
|
||||
// Add content description and extended content description
|
||||
// at header for maximum compatibility
|
||||
headerModifier.add(modifier);
|
||||
} else {
|
||||
// For now, the rest should be created at extended header
|
||||
// since other positions aren't known.
|
||||
extHeaderModifier.add(modifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
// only addField an AsfExtHeaderModifier, if there is actually something to
|
||||
// change (performance)
|
||||
if (!extHeaderModifier.isEmpty()) {
|
||||
headerModifier.add(new AsfExtHeaderModifier(extHeaderModifier));
|
||||
}
|
||||
new AsfStreamer()
|
||||
.createModifiedCopy(new RandomAccessFileInputstream(raf),
|
||||
new RandomAccessFileOutputStream(rafTemp),
|
||||
headerModifier);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* This class represents the ASF extended header object (chunk).<br>
|
||||
* Like {@link AsfHeader} it contains multiple other ASF objects (chunks).<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public final class AsfExtendedHeader extends ChunkContainer {
|
||||
|
||||
/**
|
||||
* Creates an instance.<br>
|
||||
*
|
||||
* @param pos Position within the stream.<br>
|
||||
* @param length the length of the extended header object.
|
||||
*/
|
||||
public AsfExtendedHeader(final long pos, final BigInteger length) {
|
||||
super(GUID.GUID_HEADER_EXTENSION, pos, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the contentDescription.
|
||||
*/
|
||||
public ContentDescription getContentDescription() {
|
||||
return (ContentDescription) getFirst(GUID.GUID_CONTENTDESCRIPTION,
|
||||
ContentDescription.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the tagHeader.
|
||||
*/
|
||||
public MetadataContainer getExtendedContentDescription() {
|
||||
return (MetadataContainer) getFirst(
|
||||
GUID.GUID_EXTENDED_CONTENT_DESCRIPTION, MetadataContainer.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a language list object if present.
|
||||
*
|
||||
* @return a language list object.
|
||||
*/
|
||||
public LanguageList getLanguageList() {
|
||||
return (LanguageList) getFirst(GUID.GUID_LANGUAGE_LIST,
|
||||
LanguageList.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a metadata library object if present.
|
||||
*
|
||||
* @return metadata library objet
|
||||
*/
|
||||
public MetadataContainer getMetadataLibraryObject() {
|
||||
return (MetadataContainer) getFirst(GUID.GUID_METADATA_LIBRARY,
|
||||
MetadataContainer.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a metadata object if present.
|
||||
*
|
||||
* @return metadata object
|
||||
*/
|
||||
public MetadataContainer getMetadataObject() {
|
||||
return (MetadataContainer) getFirst(GUID.GUID_METADATA,
|
||||
MetadataContainer.class);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,213 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Each ASF file starts with a so called header. <br>
|
||||
* This header contains other chunks. Each chunk starts with a 16 byte GUID
|
||||
* followed by the length (in bytes) of the chunk (including GUID). The length
|
||||
* number takes 8 bytes and is unsigned. Finally the chunk's data appears. <br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public final class AsfHeader extends ChunkContainer {
|
||||
/**
|
||||
* The charset "UTF-16LE" is mandatory for ASF handling.
|
||||
*/
|
||||
public final static Charset ASF_CHARSET = Charset.forName("UTF-16LE"); //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Byte sequence representing the zero term character.
|
||||
*/
|
||||
public final static byte[] ZERO_TERM = {0, 0};
|
||||
|
||||
static {
|
||||
Set<GUID> MULTI_CHUNKS = new HashSet<GUID>();
|
||||
MULTI_CHUNKS.add(GUID.GUID_STREAM);
|
||||
}
|
||||
|
||||
/**
|
||||
* An ASF header contains multiple chunks. <br>
|
||||
* The count of those is stored here.
|
||||
*/
|
||||
private final long chunkCount;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param pos see {@link Chunk#position}
|
||||
* @param chunkLen see {@link Chunk#chunkLength}
|
||||
* @param chunkCnt
|
||||
*/
|
||||
public AsfHeader(final long pos, final BigInteger chunkLen,
|
||||
final long chunkCnt) {
|
||||
super(GUID.GUID_HEADER, pos, chunkLen);
|
||||
this.chunkCount = chunkCnt;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method looks for an content description object in this header
|
||||
* instance, if not found there, it tries to get one from a contained ASF
|
||||
* header extension object.
|
||||
*
|
||||
* @return content description if found, <code>null</code> otherwise.
|
||||
*/
|
||||
public ContentDescription findContentDescription() {
|
||||
ContentDescription result = getContentDescription();
|
||||
if (result == null && getExtendedHeader() != null) {
|
||||
result = getExtendedHeader().getContentDescription();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method looks for an extended content description object in this
|
||||
* header instance, if not found there, it tries to get one from a contained
|
||||
* ASF header extension object.
|
||||
*
|
||||
* @return extended content description if found, <code>null</code>
|
||||
* otherwise.
|
||||
*/
|
||||
public MetadataContainer findExtendedContentDescription() {
|
||||
MetadataContainer result = getExtendedContentDescription();
|
||||
if (result == null && getExtendedHeader() != null) {
|
||||
result = getExtendedHeader().getExtendedContentDescription();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method searches for a metadata container of the given type.<br>
|
||||
*
|
||||
* @param type the type of the container to look up.
|
||||
* @return a container of specified type, of <code>null</code> if not
|
||||
* contained.
|
||||
*/
|
||||
public MetadataContainer findMetadataContainer(final ContainerType type) {
|
||||
MetadataContainer result = (MetadataContainer) getFirst(type
|
||||
.getContainerGUID(), MetadataContainer.class);
|
||||
if (result == null) {
|
||||
result = (MetadataContainer) getExtendedHeader().getFirst(
|
||||
type.getContainerGUID(), MetadataContainer.class);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the first audio stream chunk found in the asf file or
|
||||
* stream.
|
||||
*
|
||||
* @return Returns the audioStreamChunk.
|
||||
*/
|
||||
public AudioStreamChunk getAudioStreamChunk() {
|
||||
AudioStreamChunk result = null;
|
||||
final List<Chunk> streamChunks = assertChunkList(GUID.GUID_STREAM);
|
||||
for (int i = 0; i < streamChunks.size() && result == null; i++) {
|
||||
if (streamChunks.get(i) instanceof AudioStreamChunk) {
|
||||
result = (AudioStreamChunk) streamChunks.get(i);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of chunks, when this instance was created.<br>
|
||||
* If chunks have been added, this won't be reflected with this call.<br>
|
||||
* For that use {@link #getChunks()}.
|
||||
*
|
||||
* @return Chunkcount at instance creation.
|
||||
*/
|
||||
public long getChunkCount() {
|
||||
return this.chunkCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the contentDescription.
|
||||
*/
|
||||
public ContentDescription getContentDescription() {
|
||||
return (ContentDescription) getFirst(GUID.GUID_CONTENTDESCRIPTION,
|
||||
ContentDescription.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the encodingChunk.
|
||||
*/
|
||||
public EncodingChunk getEncodingChunk() {
|
||||
return (EncodingChunk) getFirst(GUID.GUID_ENCODING, EncodingChunk.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the encodingChunk.
|
||||
*/
|
||||
public EncryptionChunk getEncryptionChunk() {
|
||||
return (EncryptionChunk) getFirst(GUID.GUID_CONTENT_ENCRYPTION,
|
||||
EncryptionChunk.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the tagHeader.
|
||||
*/
|
||||
public MetadataContainer getExtendedContentDescription() {
|
||||
return (MetadataContainer) getFirst(
|
||||
GUID.GUID_EXTENDED_CONTENT_DESCRIPTION, MetadataContainer.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the extended header.
|
||||
*/
|
||||
public AsfExtendedHeader getExtendedHeader() {
|
||||
return (AsfExtendedHeader) getFirst(GUID.GUID_HEADER_EXTENSION,
|
||||
AsfExtendedHeader.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the fileHeader.
|
||||
*/
|
||||
public FileHeader getFileHeader() {
|
||||
return (FileHeader) getFirst(GUID.GUID_FILE, FileHeader.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the streamBitratePropertiesChunk.
|
||||
*/
|
||||
public StreamBitratePropertiesChunk getStreamBitratePropertiesChunk() {
|
||||
return (StreamBitratePropertiesChunk) getFirst(
|
||||
GUID.GUID_STREAM_BITRATE_PROPERTIES,
|
||||
StreamBitratePropertiesChunk.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String prettyPrint(final String prefix) {
|
||||
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix,
|
||||
prefix + " | : Contains: \"" + getChunkCount() + "\" chunks"
|
||||
+ Utils.LINE_SEPARATOR));
|
||||
return result.toString();
|
||||
}
|
||||
}
|
|
@ -1,302 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* This class represents the stream chunk describing an audio stream. <br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public final class AudioStreamChunk extends StreamChunk {
|
||||
/**
|
||||
* Stores the hex values of codec identifiers to their descriptions. <br>
|
||||
*/
|
||||
public final static String[][] CODEC_DESCRIPTIONS = {
|
||||
{"161", " (Windows Media Audio (ver 7,8,9))"},
|
||||
{"162", " (Windows Media Audio 9 series (Professional))"},
|
||||
{"163", "(Windows Media Audio 9 series (Lossless))"},
|
||||
{"7A21", " (GSM-AMR (CBR))"}, {"7A22", " (GSM-AMR (VBR))"}};
|
||||
/**
|
||||
* Stores the audio codec number for WMA
|
||||
*/
|
||||
public final static long WMA = 0x161;
|
||||
/**
|
||||
* Stores the audio codec number for WMA (CBR)
|
||||
*/
|
||||
public final static long WMA_CBR = 0x7A21;
|
||||
/**
|
||||
* Stores the audio codec number for WMA_LOSSLESS
|
||||
*/
|
||||
public final static long WMA_LOSSLESS = 0x163;
|
||||
/**
|
||||
* Stores the audio codec number for WMA_PRO
|
||||
*/
|
||||
public final static long WMA_PRO = 0x162;
|
||||
|
||||
/**
|
||||
* Stores the audio codec number for WMA (VBR)
|
||||
*/
|
||||
public final static long WMA_VBR = 0x7A22;
|
||||
|
||||
/**
|
||||
* Stores the average amount of bytes used by audio stream. <br>
|
||||
* This value is a field within type specific data of audio stream. Maybe it
|
||||
* could be used to calculate the KBPs.
|
||||
*/
|
||||
private long averageBytesPerSec;
|
||||
|
||||
/**
|
||||
* Amount of bits used per sample. <br>
|
||||
*/
|
||||
private int bitsPerSample;
|
||||
|
||||
/**
|
||||
* The block alignment of the audio data.
|
||||
*/
|
||||
private long blockAlignment;
|
||||
|
||||
/**
|
||||
* Number of channels.
|
||||
*/
|
||||
private long channelCount;
|
||||
|
||||
/**
|
||||
* Some data which needs to be interpreted if the codec is handled.
|
||||
*/
|
||||
private byte[] codecData = new byte[0];
|
||||
|
||||
/**
|
||||
* The audio compression format code.
|
||||
*/
|
||||
private long compressionFormat;
|
||||
|
||||
/**
|
||||
* this field stores the error concealment type.
|
||||
*/
|
||||
private GUID errorConcealment;
|
||||
|
||||
/**
|
||||
* Sampling rate of audio stream.
|
||||
*/
|
||||
private long samplingRate;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param chunkLen Length of the entire chunk (including guid and size)
|
||||
*/
|
||||
public AudioStreamChunk(final BigInteger chunkLen) {
|
||||
super(GUID.GUID_AUDIOSTREAM, chunkLen);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the averageBytesPerSec.
|
||||
*/
|
||||
public long getAverageBytesPerSec() {
|
||||
return this.averageBytesPerSec;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param avgeBytesPerSec The averageBytesPerSec to set.
|
||||
*/
|
||||
public void setAverageBytesPerSec(final long avgeBytesPerSec) {
|
||||
this.averageBytesPerSec = avgeBytesPerSec;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the bitsPerSample.
|
||||
*/
|
||||
public int getBitsPerSample() {
|
||||
return this.bitsPerSample;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bitsPerSample
|
||||
*
|
||||
* @param bps
|
||||
*/
|
||||
public void setBitsPerSample(final int bps) {
|
||||
this.bitsPerSample = bps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the blockAlignment.
|
||||
*/
|
||||
public long getBlockAlignment() {
|
||||
return this.blockAlignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the blockAlignment.
|
||||
*
|
||||
* @param align
|
||||
*/
|
||||
public void setBlockAlignment(final long align) {
|
||||
this.blockAlignment = align;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the channelCount.
|
||||
*/
|
||||
public long getChannelCount() {
|
||||
return this.channelCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param channels The channelCount to set.
|
||||
*/
|
||||
public void setChannelCount(final long channels) {
|
||||
this.channelCount = channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the codecData.
|
||||
*/
|
||||
public byte[] getCodecData() {
|
||||
return this.codecData.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the codecData
|
||||
*
|
||||
* @param codecSpecificData
|
||||
*/
|
||||
public void setCodecData(final byte[] codecSpecificData) {
|
||||
if (codecSpecificData == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
this.codecData = codecSpecificData.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will take a look at {@link #compressionFormat}and returns a
|
||||
* String with its hex value and if known a textual note on what coded it
|
||||
* represents. <br>
|
||||
*
|
||||
* @return A description for the used codec.
|
||||
*/
|
||||
public String getCodecDescription() {
|
||||
final StringBuilder result = new StringBuilder(Long
|
||||
.toHexString(getCompressionFormat()));
|
||||
String furtherDesc = " (Unknown)";
|
||||
for (final String[] aCODEC_DESCRIPTIONS : CODEC_DESCRIPTIONS) {
|
||||
if (aCODEC_DESCRIPTIONS[0].equalsIgnoreCase(result.toString())) {
|
||||
furtherDesc = aCODEC_DESCRIPTIONS[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result.length() % 2 == 0) {
|
||||
result.insert(0, "0x");
|
||||
} else {
|
||||
result.insert(0, "0x0");
|
||||
}
|
||||
result.append(furtherDesc);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the compressionFormat.
|
||||
*/
|
||||
public long getCompressionFormat() {
|
||||
return this.compressionFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cFormatCode The compressionFormat to set.
|
||||
*/
|
||||
public void setCompressionFormat(final long cFormatCode) {
|
||||
this.compressionFormat = cFormatCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the errorConcealment.
|
||||
*/
|
||||
public GUID getErrorConcealment() {
|
||||
return this.errorConcealment;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the error concealment type which is given by two GUIDs. <br>
|
||||
*
|
||||
* @param errConc the type of error concealment the audio stream is stored as.
|
||||
*/
|
||||
public void setErrorConcealment(final GUID errConc) {
|
||||
this.errorConcealment = errConc;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes the value of {@link #getAverageBytesPerSec()}and
|
||||
* calculates the kbps out of it, by simply multiplying by 8 and dividing by
|
||||
* 1000. <br>
|
||||
*
|
||||
* @return amount of bits per second in kilo bits.
|
||||
*/
|
||||
public int getKbps() {
|
||||
return (int) getAverageBytesPerSec() * 8 / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the samplingRate.
|
||||
*/
|
||||
public long getSamplingRate() {
|
||||
return this.samplingRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sampRate The samplingRate to set.
|
||||
*/
|
||||
public void setSamplingRate(final long sampRate) {
|
||||
this.samplingRate = sampRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* This mehtod returns whether the audio stream data is error concealed. <br>
|
||||
* For now only interleaved concealment is known. <br>
|
||||
*
|
||||
* @return <code>true</code> if error concealment is used.
|
||||
*/
|
||||
public boolean isErrorConcealed() {
|
||||
return getErrorConcealment().equals(
|
||||
GUID.GUID_AUDIO_ERROR_CONCEALEMENT_INTERLEAVED);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String prettyPrint(final String prefix) {
|
||||
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||
result.append(prefix).append(" |-> Audio info:").append(
|
||||
Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" | : Bitrate : ").append(getKbps())
|
||||
.append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" | : Channels : ").append(
|
||||
getChannelCount()).append(" at ").append(getSamplingRate())
|
||||
.append(" Hz").append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" | : Bits per Sample: ").append(
|
||||
getBitsPerSample()).append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" | : Formatcode: ").append(
|
||||
getCodecDescription()).append(Utils.LINE_SEPARATOR);
|
||||
return result.toString();
|
||||
}
|
||||
}
|
|
@ -1,175 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* This class represents a chunk within ASF streams. <br>
|
||||
* Each chunk starts with a 16byte {@linkplain GUID GUID} identifying the type.
|
||||
* After that a number (represented by 8 bytes) follows which shows the size in
|
||||
* bytes of the chunk. Finally there is the data of the chunk.
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class Chunk {
|
||||
|
||||
/**
|
||||
* The length of current chunk. <br>
|
||||
*/
|
||||
protected final BigInteger chunkLength;
|
||||
|
||||
/**
|
||||
* The GUID of represented chunk header.
|
||||
*/
|
||||
protected final GUID guid;
|
||||
|
||||
/**
|
||||
* The position of current header object within file or stream.
|
||||
*/
|
||||
protected long position;
|
||||
|
||||
/**
|
||||
* Creates an instance
|
||||
*
|
||||
* @param headerGuid The GUID of header object.
|
||||
* @param chunkLen Length of current chunk.
|
||||
*/
|
||||
public Chunk(final GUID headerGuid, final BigInteger chunkLen) {
|
||||
if (headerGuid == null) {
|
||||
throw new IllegalArgumentException("GUID must not be null.");
|
||||
}
|
||||
if (chunkLen == null || chunkLen.compareTo(BigInteger.ZERO) < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"chunkLen must not be null nor negative.");
|
||||
}
|
||||
this.guid = headerGuid;
|
||||
this.chunkLength = chunkLen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance
|
||||
*
|
||||
* @param headerGuid The GUID of header object.
|
||||
* @param pos Position of header object within stream or file.
|
||||
* @param chunkLen Length of current chunk.
|
||||
*/
|
||||
public Chunk(final GUID headerGuid, final long pos,
|
||||
final BigInteger chunkLen) {
|
||||
if (headerGuid == null) {
|
||||
throw new IllegalArgumentException("GUID must not be null");
|
||||
}
|
||||
if (pos < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Position of header can't be negative.");
|
||||
}
|
||||
if (chunkLen == null || chunkLen.compareTo(BigInteger.ZERO) < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"chunkLen must not be null nor negative.");
|
||||
}
|
||||
this.guid = headerGuid;
|
||||
this.position = pos;
|
||||
this.chunkLength = chunkLen;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the End of the current chunk introduced by current
|
||||
* header object.
|
||||
*
|
||||
* @return Position after current chunk.
|
||||
* @deprecated typo, use {@link #getChunkEnd()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public long getChunckEnd() {
|
||||
return this.position + this.chunkLength.longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the End of the current chunk introduced by current
|
||||
* header object.
|
||||
*
|
||||
* @return Position after current chunk.
|
||||
*/
|
||||
public long getChunkEnd() {
|
||||
return this.position + this.chunkLength.longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the chunkLength.
|
||||
*/
|
||||
public BigInteger getChunkLength() {
|
||||
return this.chunkLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the guid.
|
||||
*/
|
||||
public GUID getGuid() {
|
||||
return this.guid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the position.
|
||||
*/
|
||||
public long getPosition() {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the position.
|
||||
*
|
||||
* @param pos position to set.
|
||||
*/
|
||||
public void setPosition(final long pos) {
|
||||
this.position = pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates a String containing useful information prepared to be
|
||||
* printed on STD-OUT. <br>
|
||||
* This method is intended to be overwritten by inheriting classes.
|
||||
*
|
||||
* @param prefix each line gets this string prepended.
|
||||
* @return Information of current Chunk Object.
|
||||
*/
|
||||
public String prettyPrint(final String prefix) {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
result.append(prefix).append("-> GUID: ").append(
|
||||
GUID.getGuidDescription(this.guid))
|
||||
.append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" | : Starts at position: ").append(
|
||||
getPosition()).append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" | : Last byte at: ").append(
|
||||
getChunkEnd() - 1).append(Utils.LINE_SEPARATOR);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return prettyPrint("");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,176 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import org.jaudiotagger.audio.asf.util.ChunkPositionComparator;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Stores multiple ASF objects (chunks) in form of {@link Chunk} objects, and is
|
||||
* itself an ASF object (chunk).<br>
|
||||
* <br>
|
||||
* Because current implementation is solely used for ASF metadata, all chunks
|
||||
* (except for {@link StreamChunk}) may only be {@linkplain #addChunk(Chunk)
|
||||
* inserted} once.
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class ChunkContainer extends Chunk {
|
||||
|
||||
/**
|
||||
* Stores the {@link GUID} instances, which are allowed multiple times
|
||||
* within an ASF header.
|
||||
*/
|
||||
private final static Set<GUID> MULTI_CHUNKS;
|
||||
|
||||
static {
|
||||
MULTI_CHUNKS = new HashSet<GUID>();
|
||||
MULTI_CHUNKS.add(GUID.GUID_STREAM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the {@link Chunk} objects to their {@link GUID}.
|
||||
*/
|
||||
private final Map<GUID, List<Chunk>> chunkTable;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param chunkGUID the GUID which identifies the chunk.
|
||||
* @param pos the position of the chunk within the stream.
|
||||
* @param length the length of the chunk.
|
||||
*/
|
||||
public ChunkContainer(final GUID chunkGUID, final long pos,
|
||||
final BigInteger length) {
|
||||
super(chunkGUID, pos, length);
|
||||
this.chunkTable = new Hashtable<GUID, List<Chunk>>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether all stored chunks have a unique starting position among
|
||||
* their brothers.
|
||||
*
|
||||
* @param container the container to test.
|
||||
* @return <code>true</code> if all chunks are located at an unique
|
||||
* position. However, no intersection is tested.
|
||||
*/
|
||||
protected static boolean chunkstartsUnique(final ChunkContainer container) {
|
||||
boolean result = true;
|
||||
final Set<Long> chunkStarts = new HashSet<Long>();
|
||||
final Collection<Chunk> chunks = container.getChunks();
|
||||
for (final Chunk curr : chunks) {
|
||||
result &= chunkStarts.add(curr.getPosition());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a chunk to the container.<br>
|
||||
*
|
||||
* @param toAdd The chunk which is to be added.
|
||||
* @throws IllegalArgumentException If a chunk of same type is already added, except for
|
||||
* {@link StreamChunk}.
|
||||
*/
|
||||
public void addChunk(final Chunk toAdd) {
|
||||
final List<Chunk> list = assertChunkList(toAdd.getGuid());
|
||||
if (!list.isEmpty() && !MULTI_CHUNKS.contains(toAdd.getGuid())) {
|
||||
throw new IllegalArgumentException(
|
||||
"The GUID of the given chunk indicates, that there is no more instance allowed."); //$NON-NLS-1$
|
||||
}
|
||||
list.add(toAdd);
|
||||
assert chunkstartsUnique(this) : "Chunk has equal start position like an already inserted one."; //$NON-NLS-1$
|
||||
}
|
||||
|
||||
/**
|
||||
* This method asserts that a {@link List} exists for the given {@link GUID}
|
||||
* , in {@link #chunkTable}.<br>
|
||||
*
|
||||
* @param lookFor The GUID to get list for.
|
||||
* @return an already existing, or newly created list.
|
||||
*/
|
||||
protected List<Chunk> assertChunkList(final GUID lookFor) {
|
||||
List<Chunk> result = this.chunkTable.get(lookFor);
|
||||
if (result == null) {
|
||||
result = new ArrayList<Chunk>();
|
||||
this.chunkTable.put(lookFor, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of all contained chunks.<br>
|
||||
*
|
||||
* @return all contained chunks
|
||||
*/
|
||||
public Collection<Chunk> getChunks() {
|
||||
final List<Chunk> result = new ArrayList<Chunk>();
|
||||
for (final List<Chunk> curr : this.chunkTable.values()) {
|
||||
result.addAll(curr);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for the first stored chunk which has the given GUID.
|
||||
*
|
||||
* @param lookFor GUID to look up.
|
||||
* @param instanceOf The class which must additionally be matched.
|
||||
* @return <code>null</code> if no chunk was found, or the stored instance
|
||||
* doesn't match.
|
||||
*/
|
||||
protected Chunk getFirst(final GUID lookFor,
|
||||
final Class<? extends Chunk> instanceOf) {
|
||||
Chunk result = null;
|
||||
final List<Chunk> list = this.chunkTable.get(lookFor);
|
||||
if (list != null && !list.isEmpty()) {
|
||||
final Chunk chunk = list.get(0);
|
||||
if (instanceOf.isAssignableFrom(chunk.getClass())) {
|
||||
result = chunk;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if a chunk has been {@linkplain #addChunk(Chunk)
|
||||
* added} with specified {@linkplain Chunk#getGuid() GUID}.<br>
|
||||
*
|
||||
* @param lookFor GUID to look up.
|
||||
* @return <code>true</code> if chunk with specified GUID has been added.
|
||||
*/
|
||||
public boolean hasChunkByGUID(final GUID lookFor) {
|
||||
return this.chunkTable.containsKey(lookFor);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String prettyPrint(final String prefix) {
|
||||
return prettyPrint(prefix, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Nearly the same as {@link #prettyPrint(String)} however, additional
|
||||
* information can be injected below the {@link Chunk#prettyPrint(String)}
|
||||
* output and the listing of the contained chunks.<br>
|
||||
*
|
||||
* @param prefix The prefix to prepend.
|
||||
* @param containerInfo Information to inject.
|
||||
* @return Information of current Chunk Object.
|
||||
*/
|
||||
public String prettyPrint(final String prefix, final String containerInfo) {
|
||||
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||
result.append(containerInfo);
|
||||
result.append(prefix).append(" |").append(Utils.LINE_SEPARATOR);
|
||||
final ArrayList<Chunk> list = new ArrayList<Chunk>(getChunks());
|
||||
Collections.sort(list, new ChunkPositionComparator());
|
||||
|
||||
for (Chunk curr : list) {
|
||||
result.append(curr.prettyPrint(prefix + " |"));
|
||||
result.append(prefix).append(" |").append(Utils.LINE_SEPARATOR);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
|
@ -1,283 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
import org.jaudiotagger.logging.ErrorMessage;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Enumerates capabilities, respectively uses, of metadata descriptors.<br>
|
||||
* <br>
|
||||
* The {@link #METADATA_LIBRARY_OBJECT} allows the most variations of data, as
|
||||
* well as no size limitation (if it can be stored within a DWORD amount of
|
||||
* bytes).<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public enum ContainerType {
|
||||
|
||||
/**
|
||||
* The descriptor is used in the content branding object (chunk)
|
||||
*/
|
||||
CONTENT_BRANDING(GUID.GUID_CONTENT_BRANDING, 32, false, false, false, false),
|
||||
|
||||
/**
|
||||
* The descriptor is used in the content description object (chunk), so
|
||||
* {@linkplain MetadataDescriptor#DWORD_MAXVALUE maximum data length}
|
||||
* applies, no language index and stream number are allowed, as well as no
|
||||
* multiple values.
|
||||
*/
|
||||
CONTENT_DESCRIPTION(GUID.GUID_CONTENTDESCRIPTION, 16, false, false, false,
|
||||
false),
|
||||
/**
|
||||
* The descriptor is used in an extended content description object, so the
|
||||
* {@linkplain MetadataDescriptor#DWORD_MAXVALUE maximum data size} applies,
|
||||
* and no language index and stream number other than "0" is
|
||||
* allowed. Additionally no multiple values are permitted.
|
||||
*/
|
||||
EXTENDED_CONTENT(GUID.GUID_EXTENDED_CONTENT_DESCRIPTION, 16, false, false,
|
||||
false, false),
|
||||
/**
|
||||
* The descriptor is used in a metadata library object. No real size limit
|
||||
* (except DWORD range) applies. Stream numbers and language indexes can be
|
||||
* specified.
|
||||
*/
|
||||
METADATA_LIBRARY_OBJECT(GUID.GUID_METADATA_LIBRARY, 32, true, true, true,
|
||||
true),
|
||||
/**
|
||||
* The descriptor is used in a metadata object. The
|
||||
* {@linkplain MetadataDescriptor#DWORD_MAXVALUE maximum data size} applies.
|
||||
* Stream numbers can be specified. But no language index (always
|
||||
* "0").
|
||||
*/
|
||||
METADATA_OBJECT(GUID.GUID_METADATA, 16, false, true, false, true);
|
||||
|
||||
/**
|
||||
* Stores the guid that identifies ASF chunks which store metadata of the
|
||||
* current type.
|
||||
*/
|
||||
private final GUID containerGUID;
|
||||
/**
|
||||
* <code>true</code> if the descriptor field can store {@link GUID} values.
|
||||
*/
|
||||
private final boolean guidEnabled;
|
||||
/**
|
||||
* <code>true</code> if descriptor field can refer to a language.
|
||||
*/
|
||||
private final boolean languageEnabled;
|
||||
/**
|
||||
* The maximum amount of bytes the descriptor data may consume.<br>
|
||||
*/
|
||||
private final BigInteger maximumDataLength;
|
||||
/**
|
||||
* <code>true</code> if the container may store multiple values of the same
|
||||
* metadata descriptor specification (equality on name, language, and
|
||||
* stream).<br>
|
||||
* WindowsMedia players advanced tag editor for example stores the
|
||||
* WM/Picture attribute once in the extended content description, and all
|
||||
* others in the metadata library object.
|
||||
*/
|
||||
private final boolean multiValued;
|
||||
/**
|
||||
* if <code>-1</code> a size value has to be compared against
|
||||
* {@link #maximumDataLength} because {@link Long#MAX_VALUE} is exceeded.<br>
|
||||
* Otherwise this is the {@link BigInteger#longValue()} representation.
|
||||
*/
|
||||
private final long perfMaxDataLen;
|
||||
/**
|
||||
* <code>true</code> if descriptor field can refer to specific streams.
|
||||
*/
|
||||
private final boolean streamEnabled;
|
||||
|
||||
/**
|
||||
* Creates an instance
|
||||
*
|
||||
* @param guid see {@link #containerGUID}
|
||||
* @param maxDataLenBits The amount of bits that is used to represent an unsigned value
|
||||
* for the containers size descriptors. Will create a maximum
|
||||
* value for {@link #maximumDataLength}. (2 ^ maxDataLenBits -1)
|
||||
* @param guidAllowed see {@link #guidEnabled}
|
||||
* @param stream see {@link #streamEnabled}
|
||||
* @param language see {@link #languageEnabled}
|
||||
* @param multiValue see {@link #multiValued}
|
||||
*/
|
||||
ContainerType(final GUID guid, final int maxDataLenBits,
|
||||
final boolean guidAllowed, final boolean stream,
|
||||
final boolean language, final boolean multiValue) {
|
||||
this.containerGUID = guid;
|
||||
this.maximumDataLength = BigInteger.valueOf(2).pow(maxDataLenBits)
|
||||
.subtract(BigInteger.ONE);
|
||||
if (this.maximumDataLength
|
||||
.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) {
|
||||
this.perfMaxDataLen = this.maximumDataLength.longValue();
|
||||
} else {
|
||||
this.perfMaxDataLen = -1;
|
||||
}
|
||||
this.guidEnabled = guidAllowed;
|
||||
this.streamEnabled = stream;
|
||||
this.languageEnabled = language;
|
||||
this.multiValued = multiValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if low has <= index as high, in respect to
|
||||
* {@link #getOrdered()}
|
||||
*
|
||||
* @param low
|
||||
* @param high
|
||||
* @return <code>true</code> if in correct order.
|
||||
*/
|
||||
public static boolean areInCorrectOrder(final ContainerType low,
|
||||
final ContainerType high) {
|
||||
final List<ContainerType> asList = Arrays.asList(getOrdered());
|
||||
return asList.indexOf(low) <= asList.indexOf(high);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the elements in an order, that indicates more capabilities
|
||||
* (ascending).<br>
|
||||
*
|
||||
* @return capability ordered types
|
||||
*/
|
||||
public static ContainerType[] getOrdered() {
|
||||
return new ContainerType[]{CONTENT_DESCRIPTION, CONTENT_BRANDING, EXTENDED_CONTENT, METADATA_OBJECT, METADATA_LIBRARY_OBJECT};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #checkConstraints(String, byte[], int, int, int)} and
|
||||
* actually throws the exception if there is one.
|
||||
*
|
||||
* @param name name of the descriptor
|
||||
* @param data content
|
||||
* @param type data type
|
||||
* @param stream stream number
|
||||
* @param language language index
|
||||
*/
|
||||
public void assertConstraints(final String name, final byte[] data,
|
||||
final int type, final int stream, final int language) {
|
||||
final RuntimeException result = checkConstraints(name, data, type,
|
||||
stream, language);
|
||||
if (result != null) {
|
||||
throw result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the values for a {@linkplain MetadataDescriptor content
|
||||
* descriptor} match the contraints of the container type, and returns a
|
||||
* {@link RuntimeException} if the requirements aren't met.
|
||||
*
|
||||
* @param name name of the descriptor
|
||||
* @param data content
|
||||
* @param type data type
|
||||
* @param stream stream number
|
||||
* @param language language index
|
||||
* @return <code>null</code> if everything is fine.
|
||||
*/
|
||||
public RuntimeException checkConstraints(final String name,
|
||||
final byte[] data, final int type, final int stream,
|
||||
final int language) {
|
||||
RuntimeException result = null;
|
||||
// TODO generate tests
|
||||
if (name == null || data == null) {
|
||||
result = new IllegalArgumentException("Arguments must not be null.");
|
||||
} else {
|
||||
if (!Utils.isStringLengthValidNullSafe(name)) {
|
||||
result = new IllegalArgumentException(
|
||||
ErrorMessage.WMA_LENGTH_OF_STRING_IS_TOO_LARGE
|
||||
.getMsg(name.length()));
|
||||
}
|
||||
}
|
||||
if (result == null && !isWithinValueRange(data.length)) {
|
||||
result = new IllegalArgumentException(
|
||||
ErrorMessage.WMA_LENGTH_OF_DATA_IS_TOO_LARGE.getMsg(
|
||||
data.length, getMaximumDataLength(),
|
||||
getContainerGUID().getDescription()));
|
||||
}
|
||||
if (result == null
|
||||
&& (stream < 0 || stream > MetadataDescriptor.MAX_STREAM_NUMBER || (!isStreamNumberEnabled() && stream != 0))) {
|
||||
final String streamAllowed = isStreamNumberEnabled() ? "0 to 127"
|
||||
: "0";
|
||||
result = new IllegalArgumentException(
|
||||
ErrorMessage.WMA_INVALID_STREAM_REFERNCE.getMsg(stream,
|
||||
streamAllowed, getContainerGUID().getDescription()));
|
||||
}
|
||||
if (result == null && type == MetadataDescriptor.TYPE_GUID
|
||||
&& !isGuidEnabled()) {
|
||||
result = new IllegalArgumentException(
|
||||
ErrorMessage.WMA_INVALID_GUID_USE.getMsg(getContainerGUID()
|
||||
.getDescription()));
|
||||
}
|
||||
if (result == null
|
||||
&& ((language != 0 && !isLanguageEnabled()) || (language < 0 || language >= MetadataDescriptor.MAX_LANG_INDEX))) {
|
||||
final String langAllowed = isStreamNumberEnabled() ? "0 to 126"
|
||||
: "0";
|
||||
result = new IllegalArgumentException(
|
||||
ErrorMessage.WMA_INVALID_LANGUAGE_USE.getMsg(language,
|
||||
getContainerGUID().getDescription(), langAllowed));
|
||||
}
|
||||
if (result == null && this == CONTENT_DESCRIPTION
|
||||
&& type != MetadataDescriptor.TYPE_STRING) {
|
||||
result = new IllegalArgumentException(
|
||||
ErrorMessage.WMA_ONLY_STRING_IN_CD.getMsg());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the containerGUID
|
||||
*/
|
||||
public GUID getContainerGUID() {
|
||||
return this.containerGUID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the maximumDataLength
|
||||
*/
|
||||
public BigInteger getMaximumDataLength() {
|
||||
return this.maximumDataLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the guidEnabled
|
||||
*/
|
||||
public boolean isGuidEnabled() {
|
||||
return this.guidEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the languageEnabled
|
||||
*/
|
||||
public boolean isLanguageEnabled() {
|
||||
return this.languageEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the given value is less than or equal to
|
||||
* {@link #getMaximumDataLength()}, and greater or equal to zero.<br>
|
||||
*
|
||||
* @param value The value to test
|
||||
* @return <code>true</code> if size restrictions for binary data are met
|
||||
* with this container type.
|
||||
*/
|
||||
public boolean isWithinValueRange(final long value) {
|
||||
return (this.perfMaxDataLen == -1 || this.perfMaxDataLen >= value)
|
||||
&& value >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the multiValued
|
||||
*/
|
||||
public boolean isMultiValued() {
|
||||
return this.multiValued;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the streamEnabled
|
||||
*/
|
||||
public boolean isStreamNumberEnabled() {
|
||||
return this.streamEnabled;
|
||||
}
|
||||
}
|
|
@ -1,209 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This structure represents the value of the content branding object, which
|
||||
* stores the banner image, the banner image URL and the copyright URL.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public final class ContentBranding extends MetadataContainer {
|
||||
|
||||
/**
|
||||
* Stores the allowed {@linkplain MetadataDescriptor#getName() descriptor
|
||||
* keys}.
|
||||
*/
|
||||
public final static Set<String> ALLOWED;
|
||||
|
||||
/**
|
||||
* Descriptor key representing the banner image.
|
||||
*/
|
||||
public final static String KEY_BANNER_IMAGE = "BANNER_IMAGE";
|
||||
|
||||
/**
|
||||
* Descriptor key representing the banner image type.<br>
|
||||
* <br>
|
||||
* <b>Known/valid values are:</b>
|
||||
* <ol>
|
||||
* <li>0: there is no image present</li>
|
||||
* <li>1: there is a BMP image</li>
|
||||
* <li>2: there is a JPEG image</li>
|
||||
* <li>3: there is a GIF image</li>
|
||||
* </ol>
|
||||
*/
|
||||
public final static String KEY_BANNER_TYPE = "BANNER_IMAGE_TYPE";
|
||||
|
||||
/**
|
||||
* Descriptor key representing the banner image URL.
|
||||
*/
|
||||
public final static String KEY_BANNER_URL = "BANNER_IMAGE_URL";
|
||||
|
||||
/**
|
||||
* Descriptor key representing the copyright URL.
|
||||
*/
|
||||
public final static String KEY_COPYRIGHT_URL = "COPYRIGHT_URL";
|
||||
|
||||
static {
|
||||
ALLOWED = new HashSet<String>();
|
||||
ALLOWED.add(KEY_BANNER_IMAGE);
|
||||
ALLOWED.add(KEY_BANNER_TYPE);
|
||||
ALLOWED.add(KEY_BANNER_URL);
|
||||
ALLOWED.add(KEY_COPYRIGHT_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*/
|
||||
public ContentBranding() {
|
||||
this(0, BigInteger.ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param pos Position of content description within file or stream
|
||||
* @param size Length of content description.
|
||||
*/
|
||||
public ContentBranding(final long pos, final BigInteger size) {
|
||||
super(ContainerType.CONTENT_BRANDING, pos, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the banner image URL.
|
||||
*
|
||||
* @return the banner image URL.
|
||||
*/
|
||||
public String getBannerImageURL() {
|
||||
return getValueFor(KEY_BANNER_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the banner image URL, if <code>imageURL</code> is not
|
||||
* blank.<br>
|
||||
*
|
||||
* @param imageURL image URL to set.
|
||||
*/
|
||||
public void setBannerImageURL(final String imageURL) {
|
||||
if (Utils.isBlank(imageURL)) {
|
||||
removeDescriptorsByName(KEY_BANNER_URL);
|
||||
} else {
|
||||
assertDescriptor(KEY_BANNER_URL).setStringValue(imageURL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the copyright URL.
|
||||
*
|
||||
* @return the banner image URL.
|
||||
*/
|
||||
public String getCopyRightURL() {
|
||||
return getValueFor(KEY_COPYRIGHT_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the copyright URL, if <code>copyRight</code> is not
|
||||
* blank.<br>
|
||||
*
|
||||
* @param copyRight copyright URL to set.
|
||||
*/
|
||||
public void setCopyRightURL(final String copyRight) {
|
||||
if (Utils.isBlank(copyRight)) {
|
||||
removeDescriptorsByName(KEY_COPYRIGHT_URL);
|
||||
} else {
|
||||
assertDescriptor(KEY_COPYRIGHT_URL).setStringValue(copyRight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public long getCurrentAsfChunkSize() {
|
||||
// GUID, size, image type, image data size, image url data size,
|
||||
// copyright data size
|
||||
long result = 40;
|
||||
result += assertDescriptor(KEY_BANNER_IMAGE,
|
||||
MetadataDescriptor.TYPE_BINARY).getRawDataSize();
|
||||
result += getBannerImageURL().length();
|
||||
result += getCopyRightURL().length();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the binary image data.
|
||||
*
|
||||
* @return binary image data.
|
||||
*/
|
||||
public byte[] getImageData() {
|
||||
return assertDescriptor(KEY_BANNER_IMAGE,
|
||||
MetadataDescriptor.TYPE_BINARY).getRawData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the image type.<br>
|
||||
*
|
||||
* @return image type
|
||||
* @see #KEY_BANNER_TYPE for known/valid values.
|
||||
*/
|
||||
public long getImageType() {
|
||||
if (!hasDescriptor(KEY_BANNER_TYPE)) {
|
||||
final MetadataDescriptor descriptor = new MetadataDescriptor(
|
||||
ContainerType.CONTENT_BRANDING, KEY_BANNER_TYPE,
|
||||
MetadataDescriptor.TYPE_DWORD);
|
||||
descriptor.setDWordValue(0);
|
||||
addDescriptor(descriptor);
|
||||
}
|
||||
return assertDescriptor(KEY_BANNER_TYPE).getNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isAddSupported(final MetadataDescriptor descriptor) {
|
||||
return ALLOWED.contains(descriptor.getName())
|
||||
&& super.isAddSupported(descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param imageType
|
||||
* @param imageData
|
||||
*/
|
||||
public void setImage(final long imageType, final byte[] imageData) {
|
||||
assert imageType >= 0 && imageType <= 3;
|
||||
assert imageType > 0 || imageData.length == 0;
|
||||
assertDescriptor(KEY_BANNER_TYPE, MetadataDescriptor.TYPE_DWORD)
|
||||
.setDWordValue(imageType);
|
||||
assertDescriptor(KEY_BANNER_IMAGE, MetadataDescriptor.TYPE_BINARY)
|
||||
.setBinaryValue(imageData);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public long writeInto(final OutputStream out) throws IOException {
|
||||
final long chunkSize = getCurrentAsfChunkSize();
|
||||
out.write(getGuid().getBytes());
|
||||
Utils.writeUINT64(chunkSize, out);
|
||||
Utils.writeUINT32(getImageType(), out);
|
||||
assert getImageType() >= 0 && getImageType() <= 3;
|
||||
final byte[] imageData = getImageData();
|
||||
assert getImageType() > 0 || imageData.length == 0;
|
||||
Utils.writeUINT32(imageData.length, out);
|
||||
out.write(imageData);
|
||||
Utils.writeUINT32(getBannerImageURL().length(), out);
|
||||
out.write(getBannerImageURL().getBytes("ASCII"));
|
||||
Utils.writeUINT32(getCopyRightURL().length(), out);
|
||||
out.write(getCopyRightURL().getBytes("ASCII"));
|
||||
return chunkSize;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,243 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This class represents the data of a chunk which contains title, author,
|
||||
* copyright, description and the rating of the file. <br>
|
||||
* It is optional within ASF files. But if, exists only once.
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public final class ContentDescription extends MetadataContainer {
|
||||
/**
|
||||
* Stores the only allowed keys of this metadata container.
|
||||
*/
|
||||
public final static Set<String> ALLOWED;
|
||||
|
||||
/**
|
||||
* Field key for author.
|
||||
*/
|
||||
public final static String KEY_AUTHOR = "AUTHOR";
|
||||
|
||||
/**
|
||||
* Field key for copyright.
|
||||
*/
|
||||
public final static String KEY_COPYRIGHT = "COPYRIGHT";
|
||||
|
||||
/**
|
||||
* Field key for description.
|
||||
*/
|
||||
public final static String KEY_DESCRIPTION = "DESCRIPTION";
|
||||
|
||||
/**
|
||||
* Field key for rating.
|
||||
*/
|
||||
public final static String KEY_RATING = "RATING";
|
||||
|
||||
/**
|
||||
* Field key for title.
|
||||
*/
|
||||
public final static String KEY_TITLE = "TITLE";
|
||||
|
||||
static {
|
||||
ALLOWED = new HashSet<String>(Arrays.asList(KEY_AUTHOR,
|
||||
KEY_COPYRIGHT, KEY_DESCRIPTION, KEY_RATING, KEY_TITLE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance. <br>
|
||||
*/
|
||||
public ContentDescription() {
|
||||
this(0, BigInteger.ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param pos Position of content description within file or stream
|
||||
* @param chunkLen Length of content description.
|
||||
*/
|
||||
public ContentDescription(final long pos, final BigInteger chunkLen) {
|
||||
super(ContainerType.CONTENT_DESCRIPTION, pos, chunkLen);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the author.
|
||||
*/
|
||||
public String getAuthor() {
|
||||
return getValueFor(KEY_AUTHOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fileAuthor The author to set.
|
||||
* @throws IllegalArgumentException If "UTF-16LE"-byte-representation would take more than 65535
|
||||
* bytes.
|
||||
*/
|
||||
public void setAuthor(final String fileAuthor) throws IllegalArgumentException {
|
||||
setStringValue(KEY_AUTHOR, fileAuthor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the comment.
|
||||
*/
|
||||
public String getComment() {
|
||||
return getValueFor(KEY_DESCRIPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tagComment The comment to set.
|
||||
* @throws IllegalArgumentException If "UTF-16LE"-byte-representation would take more than 65535
|
||||
* bytes.
|
||||
*/
|
||||
public void setComment(final String tagComment) throws IllegalArgumentException {
|
||||
setStringValue(KEY_DESCRIPTION, tagComment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the copyRight.
|
||||
*/
|
||||
public String getCopyRight() {
|
||||
return getValueFor(KEY_COPYRIGHT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public long getCurrentAsfChunkSize() {
|
||||
long result = 44; // GUID + UINT64 for size + 5 times string length
|
||||
// (each
|
||||
// 2 bytes) + 5 times zero term char (2 bytes each).
|
||||
result += getAuthor().length() * 2; // UTF-16LE
|
||||
result += getComment().length() * 2;
|
||||
result += getRating().length() * 2;
|
||||
result += getTitle().length() * 2;
|
||||
result += getCopyRight().length() * 2;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return returns the rating.
|
||||
*/
|
||||
public String getRating() {
|
||||
return getValueFor(KEY_RATING);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ratingText The rating to be set.
|
||||
* @throws IllegalArgumentException If "UTF-16LE"-byte-representation would take more than 65535
|
||||
* bytes.
|
||||
*/
|
||||
public void setRating(final String ratingText) throws IllegalArgumentException {
|
||||
setStringValue(KEY_RATING, ratingText);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the title.
|
||||
*/
|
||||
public String getTitle() {
|
||||
return getValueFor(KEY_TITLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param songTitle The title to set.
|
||||
* @throws IllegalArgumentException If "UTF-16LE"-byte-representation would take more than 65535
|
||||
* bytes.
|
||||
*/
|
||||
public void setTitle(final String songTitle) throws IllegalArgumentException {
|
||||
setStringValue(KEY_TITLE, songTitle);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isAddSupported(final MetadataDescriptor descriptor) {
|
||||
return ALLOWED.contains(descriptor.getName())
|
||||
&& super.isAddSupported(descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String prettyPrint(final String prefix) {
|
||||
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||
result.append(prefix).append(" |->Title : ").append(getTitle())
|
||||
.append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" |->Author : ").append(getAuthor())
|
||||
.append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" |->Copyright : ").append(
|
||||
getCopyRight()).append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" |->Description: ").append(getComment())
|
||||
.append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" |->Rating :").append(getRating())
|
||||
.append(Utils.LINE_SEPARATOR);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cpright The copyRight to set.
|
||||
* @throws IllegalArgumentException If "UTF-16LE"-byte-representation would take more than 65535
|
||||
* bytes.
|
||||
*/
|
||||
public void setCopyright(final String cpright) throws IllegalArgumentException {
|
||||
setStringValue(KEY_COPYRIGHT, cpright);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public long writeInto(final OutputStream out) throws IOException {
|
||||
final long chunkSize = getCurrentAsfChunkSize();
|
||||
|
||||
out.write(this.getGuid().getBytes());
|
||||
Utils.writeUINT64(getCurrentAsfChunkSize(), out);
|
||||
// write the sizes of the string representations plus 2 bytes zero term
|
||||
// character
|
||||
Utils.writeUINT16(getTitle().length() * 2 + 2, out);
|
||||
Utils.writeUINT16(getAuthor().length() * 2 + 2, out);
|
||||
Utils.writeUINT16(getCopyRight().length() * 2 + 2, out);
|
||||
Utils.writeUINT16(getComment().length() * 2 + 2, out);
|
||||
Utils.writeUINT16(getRating().length() * 2 + 2, out);
|
||||
// write the Strings
|
||||
out.write(Utils.getBytes(getTitle(), AsfHeader.ASF_CHARSET));
|
||||
out.write(AsfHeader.ZERO_TERM);
|
||||
out.write(Utils.getBytes(getAuthor(), AsfHeader.ASF_CHARSET));
|
||||
out.write(AsfHeader.ZERO_TERM);
|
||||
out.write(Utils.getBytes(getCopyRight(), AsfHeader.ASF_CHARSET));
|
||||
out.write(AsfHeader.ZERO_TERM);
|
||||
out.write(Utils.getBytes(getComment(), AsfHeader.ASF_CHARSET));
|
||||
out.write(AsfHeader.ZERO_TERM);
|
||||
out.write(Utils.getBytes(getRating(), AsfHeader.ASF_CHARSET));
|
||||
out.write(AsfHeader.ZERO_TERM);
|
||||
return chunkSize;
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class was intended to store the data of a chunk which contained the
|
||||
* encoding parameters in textual form. <br>
|
||||
* Since the needed parameters were found in other chunks the implementation of
|
||||
* this class was paused. <br>
|
||||
* TODO complete analysis.
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class EncodingChunk extends Chunk {
|
||||
|
||||
/**
|
||||
* The read strings.
|
||||
*/
|
||||
private final List<String> strings;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param chunkLen Length of current chunk.
|
||||
*/
|
||||
public EncodingChunk(final BigInteger chunkLen) {
|
||||
super(GUID.GUID_ENCODING, chunkLen);
|
||||
this.strings = new ArrayList<String>();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method appends a String.
|
||||
*
|
||||
* @param toAdd String to add.
|
||||
*/
|
||||
public void addString(final String toAdd) {
|
||||
this.strings.add(toAdd);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a collection of all {@linkplain String Strings} which
|
||||
* were added due {@link #addString(String)}.
|
||||
*
|
||||
* @return Inserted Strings.
|
||||
*/
|
||||
public Collection<String> getStrings() {
|
||||
return new ArrayList<String>(this.strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String prettyPrint(final String prefix) {
|
||||
final StringBuilder result = new StringBuilder(super
|
||||
.prettyPrint(prefix));
|
||||
this.strings.iterator();
|
||||
for (final String string : this.strings) {
|
||||
result.append(prefix).append(" | : ").append(string).append(
|
||||
Utils.LINE_SEPARATOR);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author eric
|
||||
*/
|
||||
public class EncryptionChunk extends Chunk {
|
||||
/**
|
||||
* The read strings.
|
||||
*/
|
||||
private final ArrayList<String> strings;
|
||||
private String keyID;
|
||||
private String licenseURL;
|
||||
private String protectionType;
|
||||
private String secretData;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param chunkLen Length of current chunk.
|
||||
*/
|
||||
public EncryptionChunk(final BigInteger chunkLen) {
|
||||
super(GUID.GUID_CONTENT_ENCRYPTION, chunkLen);
|
||||
this.strings = new ArrayList<String>();
|
||||
this.secretData = "";
|
||||
this.protectionType = "";
|
||||
this.keyID = "";
|
||||
this.licenseURL = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* This method appends a String.
|
||||
*
|
||||
* @param toAdd String to add.
|
||||
*/
|
||||
public void addString(final String toAdd) {
|
||||
this.strings.add(toAdd);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method gets the keyID.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getKeyID() {
|
||||
return this.keyID;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method appends a String.
|
||||
*
|
||||
* @param toAdd String to add.
|
||||
*/
|
||||
public void setKeyID(final String toAdd) {
|
||||
this.keyID = toAdd;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method gets the license URL.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getLicenseURL() {
|
||||
return this.licenseURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method appends a String.
|
||||
*
|
||||
* @param toAdd String to add.
|
||||
*/
|
||||
public void setLicenseURL(final String toAdd) {
|
||||
this.licenseURL = toAdd;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method gets the secret data.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getProtectionType() {
|
||||
return this.protectionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method appends a String.
|
||||
*
|
||||
* @param toAdd String to add.
|
||||
*/
|
||||
public void setProtectionType(final String toAdd) {
|
||||
this.protectionType = toAdd;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method gets the secret data.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getSecretData() {
|
||||
return this.secretData;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method adds the secret data.
|
||||
*
|
||||
* @param toAdd String to add.
|
||||
*/
|
||||
public void setSecretData(final String toAdd) {
|
||||
this.secretData = toAdd;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a collection of all {@link String}s which were addid
|
||||
* due {@link #addString(String)}.
|
||||
*
|
||||
* @return Inserted Strings.
|
||||
*/
|
||||
public Collection<String> getStrings() {
|
||||
return new ArrayList<String>(this.strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String prettyPrint(final String prefix) {
|
||||
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||
result.insert(0, Utils.LINE_SEPARATOR + prefix + " Encryption:"
|
||||
+ Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" |->keyID ").append(this.keyID).append(
|
||||
Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" |->secretData ").append(this.secretData)
|
||||
.append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" |->protectionType ").append(
|
||||
this.protectionType).append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" |->licenseURL ").append(this.licenseURL)
|
||||
.append(Utils.LINE_SEPARATOR);
|
||||
this.strings.iterator();
|
||||
for (final String string : this.strings) {
|
||||
result.append(prefix).append(" |->").append(string).append(Utils.LINE_SEPARATOR);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
|
@ -1,233 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* This class stores the information about the file, which is contained within a
|
||||
* special chunk of ASF files.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class FileHeader extends Chunk {
|
||||
|
||||
/**
|
||||
* Duration of the media content in 100ns steps.
|
||||
*/
|
||||
private final BigInteger duration;
|
||||
|
||||
/**
|
||||
* The time the file was created.
|
||||
*/
|
||||
private final Date fileCreationTime;
|
||||
|
||||
/**
|
||||
* Size of the file or stream.
|
||||
*/
|
||||
private final BigInteger fileSize;
|
||||
|
||||
/**
|
||||
* Usually contains value of 2.
|
||||
*/
|
||||
private final long flags;
|
||||
|
||||
/**
|
||||
* Maximum size of stream packages. <br>
|
||||
* <b>Warning: </b> must be same size as {@link #minPackageSize}. Its not
|
||||
* known how to handle deviating values.
|
||||
*/
|
||||
private final long maxPackageSize;
|
||||
|
||||
/**
|
||||
* Minimun size of stream packages. <br>
|
||||
* <b>Warning: </b> must be same size as {@link #maxPackageSize}. Its not
|
||||
* known how to handle deviating values.
|
||||
*/
|
||||
private final long minPackageSize;
|
||||
|
||||
/**
|
||||
* Number of stream packages within the File.
|
||||
*/
|
||||
private final BigInteger packageCount;
|
||||
|
||||
/**
|
||||
* No Idea of the Meaning, but stored anyway. <br>
|
||||
* Source documentation says it is: "Timestamp of end position"
|
||||
*/
|
||||
private final BigInteger timeEndPos;
|
||||
|
||||
/**
|
||||
* Like {@link #timeEndPos}no Idea.
|
||||
*/
|
||||
private final BigInteger timeStartPos;
|
||||
|
||||
/**
|
||||
* Size of an uncompressed video frame.
|
||||
*/
|
||||
private final long uncompressedFrameSize;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param chunckLen Length of the file header (chunk)
|
||||
* @param size Size of file or stream
|
||||
* @param fileTime Time file or stream was created. Time is calculated since 1st
|
||||
* january of 1601 in 100ns steps.
|
||||
* @param pkgCount Number of stream packages.
|
||||
* @param dur Duration of media clip in 100ns steps
|
||||
* @param timestampStart Timestamp of start {@link #timeStartPos}
|
||||
* @param timestampEnd Timestamp of end {@link #timeEndPos}
|
||||
* @param headerFlags some stream related flags.
|
||||
* @param minPkgSize minimum size of packages
|
||||
* @param maxPkgSize maximum size of packages
|
||||
* @param uncmpVideoFrameSize Size of an uncompressed Video Frame.
|
||||
*/
|
||||
public FileHeader(final BigInteger chunckLen, final BigInteger size,
|
||||
final BigInteger fileTime, final BigInteger pkgCount,
|
||||
final BigInteger dur, final BigInteger timestampStart,
|
||||
final BigInteger timestampEnd, final long headerFlags,
|
||||
final long minPkgSize, final long maxPkgSize,
|
||||
final long uncmpVideoFrameSize) {
|
||||
super(GUID.GUID_FILE, chunckLen);
|
||||
this.fileSize = size;
|
||||
this.packageCount = pkgCount;
|
||||
this.duration = dur;
|
||||
this.timeStartPos = timestampStart;
|
||||
this.timeEndPos = timestampEnd;
|
||||
this.flags = headerFlags;
|
||||
this.minPackageSize = minPkgSize;
|
||||
this.maxPackageSize = maxPkgSize;
|
||||
this.uncompressedFrameSize = uncmpVideoFrameSize;
|
||||
this.fileCreationTime = Utils.getDateOf(fileTime).getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the duration.
|
||||
*/
|
||||
public BigInteger getDuration() {
|
||||
return this.duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts {@link #getDuration()}from 100ns steps to normal
|
||||
* seconds.
|
||||
*
|
||||
* @return Duration of the media in seconds.
|
||||
*/
|
||||
public int getDurationInSeconds() {
|
||||
return this.duration.divide(new BigInteger("10000000")).intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the fileCreationTime.
|
||||
*/
|
||||
public Date getFileCreationTime() {
|
||||
return new Date(this.fileCreationTime.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the fileSize.
|
||||
*/
|
||||
public BigInteger getFileSize() {
|
||||
return this.fileSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the flags.
|
||||
*/
|
||||
public long getFlags() {
|
||||
return this.flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the maxPackageSize.
|
||||
*/
|
||||
public long getMaxPackageSize() {
|
||||
return this.maxPackageSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the minPackageSize.
|
||||
*/
|
||||
public long getMinPackageSize() {
|
||||
return this.minPackageSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the packageCount.
|
||||
*/
|
||||
public BigInteger getPackageCount() {
|
||||
return this.packageCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts {@link #getDuration()} from 100ns steps to normal
|
||||
* seconds with a fractional part taking milliseconds.<br>
|
||||
*
|
||||
* @return The duration of the media in seconds (with a precision of
|
||||
* milliseconds)
|
||||
*/
|
||||
public float getPreciseDuration() {
|
||||
return (float) (getDuration().doubleValue() / 10000000d);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the timeEndPos.
|
||||
*/
|
||||
public BigInteger getTimeEndPos() {
|
||||
return this.timeEndPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the timeStartPos.
|
||||
*/
|
||||
public BigInteger getTimeStartPos() {
|
||||
return this.timeStartPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the uncompressedFrameSize.
|
||||
*/
|
||||
public long getUncompressedFrameSize() {
|
||||
return this.uncompressedFrameSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see org.jaudiotagger.audio.asf.data.Chunk#prettyPrint(String)
|
||||
*/
|
||||
@Override
|
||||
public String prettyPrint(final String prefix) {
|
||||
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||
result.append(prefix).append(" |-> Filesize = ").append(
|
||||
getFileSize().toString()).append(" Bytes").append(
|
||||
Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" |-> Media duration= ").append(
|
||||
getDuration().divide(new BigInteger("10000")).toString())
|
||||
.append(" ms").append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" |-> Created at = ").append(
|
||||
getFileCreationTime()).append(Utils.LINE_SEPARATOR);
|
||||
return result.toString();
|
||||
}
|
||||
}
|
|
@ -1,527 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* This class is used for representation of GUIDs and as a reference list of all
|
||||
* Known GUIDs. <br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public final class GUID {
|
||||
|
||||
/**
|
||||
* This constant defines the GUID for stream chunks describing audio
|
||||
* streams, indicating the the audio stream has no error concealment. <br>
|
||||
*/
|
||||
public final static GUID GUID_AUDIO_ERROR_CONCEALEMENT_ABSENT = new GUID(
|
||||
new int[]{0x40, 0xA4, 0xF1, 0x49, 0xCE, 0x4E, 0xD0, 0x11, 0xA3,
|
||||
0xAC, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6},
|
||||
"Audio error concealment absent.");
|
||||
|
||||
/**
|
||||
* This constant defines the GUID for stream chunks describing audio
|
||||
* streams, indicating the the audio stream has interleaved error
|
||||
* concealment. <br>
|
||||
*/
|
||||
public final static GUID GUID_AUDIO_ERROR_CONCEALEMENT_INTERLEAVED = new GUID(
|
||||
new int[]{0x40, 0xA4, 0xF1, 0x49, 0xCE, 0x4E, 0xD0, 0x11, 0xA3,
|
||||
0xAC, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6},
|
||||
"Interleaved audio error concealment.");
|
||||
|
||||
/**
|
||||
* This constant stores the GUID indicating that stream type is audio.
|
||||
*/
|
||||
public final static GUID GUID_AUDIOSTREAM = new GUID(new int[]{0x40,
|
||||
0x9E, 0x69, 0xF8, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80,
|
||||
0x5F, 0x5C, 0x44, 0x2B}, " Audio stream");
|
||||
|
||||
/**
|
||||
* This constant stores the GUID indicating a content branding object.
|
||||
*/
|
||||
public final static GUID GUID_CONTENT_BRANDING = new GUID(new int[]{0xFA,
|
||||
0xB3, 0x11, 0x22, 0x23, 0xBD, 0xD2, 0x11, 0xB4, 0xB7, 0x00, 0xA0,
|
||||
0xC9, 0x55, 0xFC, 0x6E}, "Content Branding");
|
||||
|
||||
/**
|
||||
* This is for the Content Encryption Object
|
||||
* 2211B3FB-BD23-11D2-B4B7-00A0C955FC6E, needs to be little-endian.
|
||||
*/
|
||||
public final static GUID GUID_CONTENT_ENCRYPTION = new GUID(new int[]{
|
||||
0xfb, 0xb3, 0x11, 0x22, 0x23, 0xbd, 0xd2, 0x11, 0xb4, 0xb7, 0x00,
|
||||
0xa0, 0xc9, 0x55, 0xfc, 0x6e}, "Content Encryption Object");
|
||||
|
||||
/**
|
||||
* This constant represents the guidData for a chunk which contains Title,
|
||||
* author, copyright, description and rating.
|
||||
*/
|
||||
public final static GUID GUID_CONTENTDESCRIPTION = new GUID(new int[]{
|
||||
0x33, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00,
|
||||
0xAA, 0x00, 0x62, 0xCE, 0x6C}, "Content Description");
|
||||
|
||||
/**
|
||||
* This constant stores the GUID for Encoding-Info chunks.
|
||||
*/
|
||||
public final static GUID GUID_ENCODING = new GUID(new int[]{0x40, 0x52,
|
||||
0xD1, 0x86, 0x1D, 0x31, 0xD0, 0x11, 0xA3, 0xA4, 0x00, 0xA0, 0xC9,
|
||||
0x03, 0x48, 0xF6}, "Encoding description");
|
||||
|
||||
/**
|
||||
* This constant defines the GUID for a WMA "Extended Content Description"
|
||||
* chunk. <br>
|
||||
*/
|
||||
public final static GUID GUID_EXTENDED_CONTENT_DESCRIPTION = new GUID(
|
||||
new int[]{0x40, 0xA4, 0xD0, 0xD2, 0x07, 0xE3, 0xD2, 0x11, 0x97,
|
||||
0xF0, 0x00, 0xA0, 0xC9, 0x5E, 0xA8, 0x50},
|
||||
"Extended Content Description");
|
||||
|
||||
/**
|
||||
* GUID of ASF file header.
|
||||
*/
|
||||
public final static GUID GUID_FILE = new GUID(new int[]{0xA1, 0xDC, 0xAB,
|
||||
0x8C, 0x47, 0xA9, 0xCF, 0x11, 0x8E, 0xE4, 0x00, 0xC0, 0x0C, 0x20,
|
||||
0x53, 0x65}, "File header");
|
||||
|
||||
/**
|
||||
* This constant defines the GUID of a asf header chunk.
|
||||
*/
|
||||
public final static GUID GUID_HEADER = new GUID(new int[]{0x30, 0x26,
|
||||
0xb2, 0x75, 0x8e, 0x66, 0xcf, 0x11, 0xa6, 0xd9, 0x00, 0xaa, 0x00,
|
||||
0x62, 0xce, 0x6c}, "Asf header");
|
||||
|
||||
/**
|
||||
* This constant stores a GUID whose functionality is unknown.
|
||||
*/
|
||||
public final static GUID GUID_HEADER_EXTENSION = new GUID(new int[]{0xB5,
|
||||
0x03, 0xBF, 0x5F, 0x2E, 0xA9, 0xCF, 0x11, 0x8E, 0xE3, 0x00, 0xC0,
|
||||
0x0C, 0x20, 0x53, 0x65}, "Header Extension");
|
||||
|
||||
/**
|
||||
* This constant stores the GUID indicating the asf language list object.<br>
|
||||
*/
|
||||
public final static GUID GUID_LANGUAGE_LIST = new GUID(new int[]{0xa9,
|
||||
0x46, 0x43, 0x7c, 0xe0, 0xef, 0xfc, 0x4b, 0xb2, 0x29, 0x39, 0x3e,
|
||||
0xde, 0x41, 0x5c, 0x85}, "Language List");
|
||||
|
||||
/**
|
||||
* This constant stores the length of GUIDs used with ASF streams. <br>
|
||||
*/
|
||||
public final static int GUID_LENGTH = 16;
|
||||
|
||||
/**
|
||||
* This constant stores the GUID indicating the asf metadata object.<br>
|
||||
*/
|
||||
public final static GUID GUID_METADATA = new GUID(new int[]{0xea, 0xcb,
|
||||
0xf8, 0xc5, 0xaf, 0x5b, 0x77, 0x48, 0x84, 0x67, 0xaa, 0x8c, 0x44,
|
||||
0xfa, 0x4c, 0xca}, "Metadata");
|
||||
|
||||
/**
|
||||
* This constant stores the GUID indicating the asf metadata library object.<br>
|
||||
*/
|
||||
public final static GUID GUID_METADATA_LIBRARY = new GUID(new int[]{0x94,
|
||||
0x1c, 0x23, 0x44, 0x98, 0x94, 0xd1, 0x49, 0xa1, 0x41, 0x1d, 0x13,
|
||||
0x4e, 0x45, 0x70, 0x54}, "Metadata Library");
|
||||
/**
|
||||
* This constant stores the GUID indicating a stream object.
|
||||
*/
|
||||
public final static GUID GUID_STREAM = new GUID(new int[]{0x91, 0x07,
|
||||
0xDC, 0xB7, 0xB7, 0xA9, 0xCF, 0x11, 0x8E, 0xE6, 0x00, 0xC0, 0x0C,
|
||||
0x20, 0x53, 0x65}, "Stream");
|
||||
/**
|
||||
* This constant stores a GUID indicating a "stream bitrate properties"
|
||||
* chunk.
|
||||
*/
|
||||
public final static GUID GUID_STREAM_BITRATE_PROPERTIES = new GUID(
|
||||
new int[]{0xCE, 0x75, 0xF8, 0x7B, 0x8D, 0x46, 0xD1, 0x11, 0x8D,
|
||||
0x82, 0x00, 0x60, 0x97, 0xC9, 0xA2, 0xB2},
|
||||
"Stream bitrate properties");
|
||||
/**
|
||||
* This constant represents a GUID implementation which can be used for
|
||||
* generic implementations, which have to provide a GUID, but do not really
|
||||
* require a specific GUID to work.
|
||||
*/
|
||||
public final static GUID GUID_UNSPECIFIED = new GUID(new int[]{0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00}, "Unspecified");
|
||||
/**
|
||||
* This constant stores the GUID indicating that stream type is video.
|
||||
*/
|
||||
public final static GUID GUID_VIDEOSTREAM = new GUID(new int[]{0xC0,
|
||||
0xEF, 0x19, 0xBC, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80,
|
||||
0x5F, 0x5C, 0x44, 0x2B}, "Video stream");
|
||||
/**
|
||||
* This field stores all known GUIDs.
|
||||
*/
|
||||
public final static GUID[] KNOWN_GUIDS;
|
||||
/**
|
||||
* This constant stores the GUID for a "script command object".<br>
|
||||
*/
|
||||
public final static GUID SCRIPT_COMMAND_OBJECT = new GUID(new int[]{0x30,
|
||||
0x1a, 0xfb, 0x1e, 0x62, 0x0b, 0xd0, 0x11, 0xa3, 0x9b, 0x00, 0xa0,
|
||||
0xc9, 0x03, 0x48, 0xf6}, "Script Command Object");
|
||||
/**
|
||||
* The GUID String values format.<br>
|
||||
*/
|
||||
private final static Pattern GUID_PATTERN = Pattern
|
||||
.compile(
|
||||
"[a-f0-9]{8}\\-[a-f0-9]{4}\\-[a-f0-9]{4}\\-[a-f0-9]{4}\\-[a-f0-9]{12}",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
/**
|
||||
* This map is used, to get the description of a GUID instance, which has
|
||||
* been created by reading.<br>
|
||||
* The map comparison is done against the {@link GUID#guidData} field. But
|
||||
* only the {@link #KNOWN_GUIDS} have a description set.
|
||||
*/
|
||||
private final static Map<GUID, GUID> GUID_TO_CONFIGURED;
|
||||
|
||||
static {
|
||||
KNOWN_GUIDS = new GUID[]{GUID_AUDIO_ERROR_CONCEALEMENT_ABSENT,
|
||||
GUID_CONTENTDESCRIPTION, GUID_AUDIOSTREAM, GUID_ENCODING,
|
||||
GUID_FILE, GUID_HEADER, GUID_STREAM,
|
||||
GUID_EXTENDED_CONTENT_DESCRIPTION, GUID_VIDEOSTREAM,
|
||||
GUID_HEADER_EXTENSION, GUID_STREAM_BITRATE_PROPERTIES,
|
||||
SCRIPT_COMMAND_OBJECT, GUID_CONTENT_ENCRYPTION,
|
||||
GUID_CONTENT_BRANDING, GUID_UNSPECIFIED, GUID_METADATA_LIBRARY,
|
||||
GUID_METADATA, GUID_LANGUAGE_LIST};
|
||||
GUID_TO_CONFIGURED = new HashMap<GUID, GUID>(KNOWN_GUIDS.length);
|
||||
for (final GUID curr : KNOWN_GUIDS) {
|
||||
assert !GUID_TO_CONFIGURED.containsKey(curr) : "Double definition: \""
|
||||
+ GUID_TO_CONFIGURED.get(curr).getDescription()
|
||||
+ "\" <-> \"" + curr.getDescription() + "\"";
|
||||
GUID_TO_CONFIGURED.put(curr, curr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores an optionally description of the GUID.
|
||||
*/
|
||||
private String description = "";
|
||||
/**
|
||||
* An instance of this class stores the value of the wrapped GUID in this
|
||||
* field. <br>
|
||||
*/
|
||||
private int[] guidData = null;
|
||||
/**
|
||||
* Stores the hash code of the object.<br>
|
||||
* <code>"-1"</code> if not determined yet.
|
||||
*/
|
||||
private int hash;
|
||||
|
||||
/**
|
||||
* Creates an instance and assigns given <code>value</code>.<br>
|
||||
*
|
||||
* @param value GUID, which should be assigned. (will be converted to int[])
|
||||
*/
|
||||
public GUID(final byte[] value) {
|
||||
assert value != null;
|
||||
final int[] tmp = new int[value.length];
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
tmp[i] = (0xFF & value[i]);
|
||||
}
|
||||
setGUID(tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance and assigns given <code>value</code>.<br>
|
||||
*
|
||||
* @param value GUID, which should be assigned.
|
||||
*/
|
||||
public GUID(final int[] value) {
|
||||
setGUID(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance like {@link #GUID(int[])}and sets the optional
|
||||
* description. <br>
|
||||
*
|
||||
* @param value GUID, which should be assigned.
|
||||
* @param desc Description for the GUID.
|
||||
*/
|
||||
public GUID(final int[] value, final String desc) {
|
||||
this(value);
|
||||
if (desc == null) {
|
||||
throw new IllegalArgumentException("Argument must not be null.");
|
||||
}
|
||||
this.description = desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance like {@link #GUID(int[])} and sets the optional
|
||||
* description. (the int[] is obtained by {@link GUID#parseGUID(String)}) <br>
|
||||
*
|
||||
* @param guidString GUID, which should be assigned.
|
||||
* @param desc Description for the GUID.
|
||||
*/
|
||||
public GUID(final String guidString, final String desc) {
|
||||
this(parseGUID(guidString).getGUID());
|
||||
if (desc == null) {
|
||||
throw new IllegalArgumentException("Argument must not be null.");
|
||||
}
|
||||
this.description = desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if the given <code>value</code> is matching the GUID
|
||||
* specification of ASF streams. <br>
|
||||
*
|
||||
* @param value possible GUID.
|
||||
* @return <code>true</code> if <code>value</code> matches the specification
|
||||
* of a GUID.
|
||||
*/
|
||||
public static boolean assertGUID(final int[] value) {
|
||||
return value != null && value.length == GUID.GUID_LENGTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method looks up a GUID instance from {@link #KNOWN_GUIDS} which
|
||||
* matches the value of the given GUID.
|
||||
*
|
||||
* @param orig GUID to look up.
|
||||
* @return a GUID instance from {@link #KNOWN_GUIDS} if available.
|
||||
* <code>null</code> else.
|
||||
*/
|
||||
public static GUID getConfigured(final GUID orig) {
|
||||
// safe against null
|
||||
return GUID_TO_CONFIGURED.get(orig);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method searches a GUID in {@link #KNOWN_GUIDS}which is equal to the
|
||||
* given <code>guidData</code> and returns its description. <br>
|
||||
* This method is useful if a GUID was read out of a file and no
|
||||
* identification has been done yet.
|
||||
*
|
||||
* @param guid GUID, which description is needed.
|
||||
* @return description of the GUID if found. Else <code>null</code>
|
||||
*/
|
||||
public static String getGuidDescription(final GUID guid) {
|
||||
String result = null;
|
||||
if (guid == null) {
|
||||
throw new IllegalArgumentException("Argument must not be null.");
|
||||
}
|
||||
if (getConfigured(guid) != null) {
|
||||
result = getConfigured(guid).getDescription();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method parses a String as GUID.<br>
|
||||
* The format is like the one in the ASF specification.<br>
|
||||
* An Example: <code>C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA</code><br>
|
||||
*
|
||||
* @param guid the string to parse.
|
||||
* @return the GUID.
|
||||
* @throws GUIDFormatException If the GUID has an invalid format.
|
||||
*/
|
||||
public static GUID parseGUID(final String guid) throws GUIDFormatException {
|
||||
if (guid == null) {
|
||||
throw new GUIDFormatException("null");
|
||||
}
|
||||
if (!GUID_PATTERN.matcher(guid).matches()) {
|
||||
throw new GUIDFormatException("Invalid guidData format.");
|
||||
}
|
||||
final int[] bytes = new int[GUID_LENGTH];
|
||||
/*
|
||||
* Don't laugh, but did not really come up with a nicer solution today
|
||||
*/
|
||||
final int[] arrayIndices = {3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12,
|
||||
13, 14, 15};
|
||||
int arrayPointer = 0;
|
||||
for (int i = 0; i < guid.length(); i++) {
|
||||
if (guid.charAt(i) == '-') {
|
||||
continue;
|
||||
}
|
||||
bytes[arrayIndices[arrayPointer++]] = Integer.parseInt(guid
|
||||
.substring(i, i + 2), 16);
|
||||
i++;
|
||||
}
|
||||
return new GUID(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method compares two objects. If the given Object is a {@link GUID},
|
||||
* the stored GUID values are compared. <br>
|
||||
*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
boolean result = false;
|
||||
if (obj instanceof GUID) {
|
||||
final GUID other = (GUID) obj;
|
||||
result = Arrays.equals(this.getGUID(), other.getGUID());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the GUID as an array of bytes. <br>
|
||||
*
|
||||
* @return The GUID as a byte array.
|
||||
* @see #getGUID()
|
||||
*/
|
||||
public byte[] getBytes() {
|
||||
final byte[] result = new byte[this.guidData.length];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = (byte) (this.guidData[i] & 0xFF);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the description.
|
||||
*/
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the GUID of this object. <br>
|
||||
*
|
||||
* @return stored GUID.
|
||||
*/
|
||||
public int[] getGUID() {
|
||||
final int[] copy = new int[this.guidData.length];
|
||||
System.arraycopy(this.guidData, 0, copy, 0, this.guidData.length);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method saves a copy of the given <code>value</code> as the
|
||||
* represented value of this object. <br>
|
||||
* The given value is checked with {@link #assertGUID(int[])}.<br>
|
||||
*
|
||||
* @param value GUID to assign.
|
||||
*/
|
||||
private void setGUID(final int[] value) {
|
||||
if (assertGUID(value)) {
|
||||
this.guidData = new int[GUID_LENGTH];
|
||||
System.arraycopy(value, 0, this.guidData, 0, GUID_LENGTH);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"The given guidData doesn't match the GUID specification.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to get 2digit hex values of each byte.
|
||||
*
|
||||
* @param bytes bytes to convert.
|
||||
* @return each byte as 2 digit hex.
|
||||
*/
|
||||
private String[] getHex(final byte[] bytes) {
|
||||
final String[] result = new String[bytes.length];
|
||||
final StringBuilder tmp = new StringBuilder();
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
tmp.delete(0, tmp.length());
|
||||
tmp.append(Integer.toHexString(0xFF & bytes[i]));
|
||||
if (tmp.length() == 1) {
|
||||
tmp.insert(0, "0");
|
||||
}
|
||||
result[i] = tmp.toString();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (this.hash == -1) {
|
||||
int tmp = 0;
|
||||
for (final int curr : getGUID()) {
|
||||
tmp = tmp * 31 + curr;
|
||||
}
|
||||
this.hash = tmp;
|
||||
}
|
||||
return this.hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if the currently stored GUID ({@link #guidData}) is
|
||||
* correctly filled. <br>
|
||||
*
|
||||
* @return <code>true</code> if it is.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return assertGUID(getGUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method gives a hex formatted representation of {@link #getGUID()}
|
||||
*
|
||||
* @return hex formatted representation.
|
||||
*/
|
||||
public String prettyPrint() {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
String descr = getDescription();
|
||||
if (Utils.isBlank(descr)) {
|
||||
descr = getGuidDescription(this);
|
||||
}
|
||||
if (!Utils.isBlank(descr)) {
|
||||
result.append("Description: ").append(descr).append(
|
||||
Utils.LINE_SEPARATOR).append(" ");
|
||||
}
|
||||
result.append(this.toString());
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
// C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA
|
||||
// 0xea, 0xcb,0xf8, 0xc5, 0xaf, 0x5b, 0x77, 0x48, 0x84, 0x67, 0xaa,
|
||||
// 0x8c, 0x44,0xfa, 0x4c, 0xca
|
||||
final StringBuilder result = new StringBuilder();
|
||||
final String[] bytes = getHex(getBytes());
|
||||
result.append(bytes[3]);
|
||||
result.append(bytes[2]);
|
||||
result.append(bytes[1]);
|
||||
result.append(bytes[0]);
|
||||
result.append('-');
|
||||
result.append(bytes[5]);
|
||||
result.append(bytes[4]);
|
||||
result.append('-');
|
||||
result.append(bytes[7]);
|
||||
result.append(bytes[6]);
|
||||
result.append('-');
|
||||
result.append(bytes[8]);
|
||||
result.append(bytes[9]);
|
||||
result.append('-');
|
||||
result.append(bytes[10]);
|
||||
result.append(bytes[11]);
|
||||
result.append(bytes[12]);
|
||||
result.append(bytes[13]);
|
||||
result.append(bytes[14]);
|
||||
result.append(bytes[15]);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
/**
|
||||
* This exception is used when a string was about to be interpreted as a GUID,
|
||||
* but did not match the format.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class GUIDFormatException extends IllegalArgumentException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 6035645678612384953L;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param detail detail message.
|
||||
*/
|
||||
public GUIDFormatException(final String detail) {
|
||||
super(detail);
|
||||
}
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
import org.jaudiotagger.logging.ErrorMessage;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This structure represents the data of the ASF language object.<br>
|
||||
* The language list is simply a listing of language codes which should comply
|
||||
* to RFC-1766.<br>
|
||||
* <b>Consider:</b> the index of a language is used by other entries in the ASF
|
||||
* metadata.
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class LanguageList extends Chunk {
|
||||
|
||||
/**
|
||||
* List of language codes, complying RFC-1766
|
||||
*/
|
||||
private final List<String> languages = new ArrayList<String>();
|
||||
|
||||
/**
|
||||
* Creates a new instance.<br>
|
||||
*/
|
||||
public LanguageList() {
|
||||
super(GUID.GUID_LANGUAGE_LIST, 0, BigInteger.ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param pos position within the ASF file.
|
||||
* @param size size of the chunk
|
||||
*/
|
||||
public LanguageList(final long pos, final BigInteger size) {
|
||||
super(GUID.GUID_LANGUAGE_LIST, pos, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method adds a language.<br>
|
||||
*
|
||||
* @param language language code
|
||||
*/
|
||||
public void addLanguage(final String language) {
|
||||
if (language.length() < MetadataDescriptor.MAX_LANG_INDEX) {
|
||||
if (!this.languages.contains(language)) {
|
||||
this.languages.add(language);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
ErrorMessage.WMA_LENGTH_OF_LANGUAGE_IS_TOO_LARGE
|
||||
.getMsg(language.length() * 2 + 2));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the language code at the specified index.
|
||||
*
|
||||
* @param index the index of the language code to get.
|
||||
* @return the language code at given index.
|
||||
*/
|
||||
public String getLanguage(final int index) {
|
||||
return this.languages.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of stored language codes.
|
||||
*
|
||||
* @return number of stored language codes.
|
||||
*/
|
||||
public int getLanguageCount() {
|
||||
return this.languages.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all language codes in list.
|
||||
*
|
||||
* @return list of language codes.
|
||||
*/
|
||||
public List<String> getLanguages() {
|
||||
return new ArrayList<String>(this.languages);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String prettyPrint(final String prefix) {
|
||||
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||
for (int i = 0; i < getLanguageCount(); i++) {
|
||||
result.append(prefix);
|
||||
result.append(" |-> ");
|
||||
result.append(i);
|
||||
result.append(" : ");
|
||||
result.append(getLanguage(i));
|
||||
result.append(Utils.LINE_SEPARATOR);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the language entry at specified index.
|
||||
*
|
||||
* @param index index of language to remove.
|
||||
*/
|
||||
public void removeLanguage(final int index) {
|
||||
this.languages.remove(index);
|
||||
}
|
||||
}
|
|
@ -1,443 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import org.jaudiotagger.audio.asf.io.WriteableChunk;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* This structure represents the "Metadata Object","Metadata
|
||||
* Library Object" and "Extended Content Description".<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class MetadataContainer extends Chunk implements WriteableChunk {
|
||||
|
||||
/**
|
||||
* stores the represented container type.<br>
|
||||
*/
|
||||
private final ContainerType containerType;
|
||||
/**
|
||||
* Stores the descriptors.
|
||||
*/
|
||||
private final Map<DescriptorPointer, List<MetadataDescriptor>> descriptors = new Hashtable<DescriptorPointer, List<MetadataDescriptor>>();
|
||||
/**
|
||||
* for performance reasons this instance is used to look up existing
|
||||
* descriptors in {@link #descriptors}.<br>
|
||||
*/
|
||||
private final DescriptorPointer perfPoint = new DescriptorPointer(
|
||||
new MetadataDescriptor(""));
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param type determines the type of the container
|
||||
*/
|
||||
public MetadataContainer(final ContainerType type) {
|
||||
this(type, 0, BigInteger.ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param type determines the type of the container
|
||||
* @param pos location in the ASF file
|
||||
* @param size size of the chunk.
|
||||
*/
|
||||
public MetadataContainer(final ContainerType type, final long pos,
|
||||
final BigInteger size) {
|
||||
super(type.getContainerGUID(), pos, size);
|
||||
this.containerType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param containerGUID the containers GUID
|
||||
* @param pos location in the ASF file
|
||||
* @param size size of the chunk.
|
||||
*/
|
||||
public MetadataContainer(final GUID containerGUID, final long pos,
|
||||
final BigInteger size) {
|
||||
this(determineType(containerGUID), pos, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up all {@linkplain ContainerType#getContainerGUID() guids} and
|
||||
* returns the matching type.
|
||||
*
|
||||
* @param guid GUID to look up
|
||||
* @return matching container type.
|
||||
* @throws IllegalArgumentException if no container type matches
|
||||
*/
|
||||
private static ContainerType determineType(final GUID guid)
|
||||
throws IllegalArgumentException {
|
||||
assert guid != null;
|
||||
ContainerType result = null;
|
||||
for (final ContainerType curr : ContainerType.values()) {
|
||||
if (curr.getContainerGUID().equals(guid)) {
|
||||
result = curr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unknown metadata container specified by GUID ("
|
||||
+ guid.toString() + ")");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a metadata descriptor.
|
||||
*
|
||||
* @param toAdd the descriptor to add.
|
||||
* @throws IllegalArgumentException if descriptor does not meet container requirements, or
|
||||
* already exist.
|
||||
*/
|
||||
public final void addDescriptor(final MetadataDescriptor toAdd)
|
||||
throws IllegalArgumentException {
|
||||
// check with throwing exceptions
|
||||
this.containerType.assertConstraints(toAdd.getName(), toAdd
|
||||
.getRawData(), toAdd.getType(), toAdd.getStreamNumber(), toAdd
|
||||
.getLanguageIndex());
|
||||
// validate containers capabilities
|
||||
if (!isAddSupported(toAdd)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Descriptor cannot be added, see isAddSupported(...)");
|
||||
}
|
||||
/*
|
||||
* Check for containers types capabilities.
|
||||
*/
|
||||
// Search for descriptor list by name, language and stream.
|
||||
List<MetadataDescriptor> list;
|
||||
synchronized (this.perfPoint) {
|
||||
list = this.descriptors.get(this.perfPoint.setDescriptor(toAdd));
|
||||
}
|
||||
if (list == null) {
|
||||
list = new ArrayList<MetadataDescriptor>();
|
||||
this.descriptors.put(new DescriptorPointer(toAdd), list);
|
||||
} else {
|
||||
if (!list.isEmpty() && !this.containerType.isMultiValued()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Container does not allow multiple values of descriptors with same name, language index and stream number");
|
||||
}
|
||||
}
|
||||
list.add(toAdd);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method asserts that this container has a descriptor with the
|
||||
* specified key, means returns an existing or creates a new descriptor.
|
||||
*
|
||||
* @param key the descriptor name to look up (or create)
|
||||
* @return the/a descriptor with the specified name (and initial type of
|
||||
* {@link MetadataDescriptor#TYPE_STRING}.
|
||||
*/
|
||||
protected final MetadataDescriptor assertDescriptor(final String key) {
|
||||
return assertDescriptor(key, MetadataDescriptor.TYPE_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method asserts that this container has a descriptor with the
|
||||
* specified key, means returns an existing or creates a new descriptor.
|
||||
*
|
||||
* @param key the descriptor name to look up (or create)
|
||||
* @param type if the descriptor is created, this data type is applied.
|
||||
* @return the/a descriptor with the specified name.
|
||||
*/
|
||||
protected final MetadataDescriptor assertDescriptor(final String key,
|
||||
final int type) {
|
||||
MetadataDescriptor desc;
|
||||
final List<MetadataDescriptor> descriptorsByName = getDescriptorsByName(key);
|
||||
if (descriptorsByName == null || descriptorsByName.isEmpty()) {
|
||||
desc = new MetadataDescriptor(getContainerType(), key, type);
|
||||
addDescriptor(desc);
|
||||
} else {
|
||||
desc = descriptorsByName.get(0);
|
||||
}
|
||||
return desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a descriptor already exists.<br>
|
||||
* Name, stream number and language index are compared. Data and data type
|
||||
* are ignored.
|
||||
*
|
||||
* @param lookup descriptor to look up.
|
||||
* @return <code>true</code> if such a descriptor already exists.
|
||||
*/
|
||||
public final boolean containsDescriptor(final MetadataDescriptor lookup) {
|
||||
assert lookup != null;
|
||||
return this.descriptors.containsKey(this.perfPoint
|
||||
.setDescriptor(lookup));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of container this instance represents.<br>
|
||||
*
|
||||
* @return represented container type.
|
||||
*/
|
||||
public final ContainerType getContainerType() {
|
||||
return this.containerType;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public long getCurrentAsfChunkSize() {
|
||||
/*
|
||||
* 16 bytes GUID, 8 bytes chunk size, 2 bytes descriptor count
|
||||
*/
|
||||
long result = 26;
|
||||
for (final MetadataDescriptor curr : getDescriptors()) {
|
||||
result += curr.getCurrentAsfSize(this.containerType);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of contained descriptors.
|
||||
*
|
||||
* @return number of descriptors.
|
||||
*/
|
||||
public final int getDescriptorCount() {
|
||||
return this.getDescriptors().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all stored descriptors.
|
||||
*
|
||||
* @return stored descriptors.
|
||||
*/
|
||||
public final List<MetadataDescriptor> getDescriptors() {
|
||||
final List<MetadataDescriptor> result = new ArrayList<MetadataDescriptor>();
|
||||
for (final List<MetadataDescriptor> curr : this.descriptors.values()) {
|
||||
result.addAll(curr);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of descriptors with the given
|
||||
* {@linkplain MetadataDescriptor#getName() name}.<br>
|
||||
*
|
||||
* @param name name of the descriptors to return
|
||||
* @return list of descriptors with given name.
|
||||
*/
|
||||
public final List<MetadataDescriptor> getDescriptorsByName(final String name) {
|
||||
assert name != null;
|
||||
final List<MetadataDescriptor> result = new ArrayList<MetadataDescriptor>();
|
||||
final Collection<List<MetadataDescriptor>> values = this.descriptors
|
||||
.values();
|
||||
for (final List<MetadataDescriptor> currList : values) {
|
||||
if (!currList.isEmpty() && currList.get(0).getName().equals(name)) {
|
||||
result.addAll(currList);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method looks up a descriptor with given name and returns its value
|
||||
* as string.<br>
|
||||
*
|
||||
* @param name the name of the descriptor to look up.
|
||||
* @return the string representation of a found descriptors value. Even an
|
||||
* empty string if no descriptor has been found.
|
||||
*/
|
||||
protected final String getValueFor(final String name) {
|
||||
String result = "";
|
||||
final List<MetadataDescriptor> descs = getDescriptorsByName(name);
|
||||
if (descs != null) {
|
||||
assert descs.size() <= 1;
|
||||
if (!descs.isEmpty()) {
|
||||
result = descs.get(0).getString();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this container contains a descriptor with given
|
||||
* {@linkplain MetadataDescriptor#getName() name}.<br>
|
||||
*
|
||||
* @param name Name of the descriptor to look for.
|
||||
* @return <code>true</code> if descriptor has been found.
|
||||
*/
|
||||
public final boolean hasDescriptor(final String name) {
|
||||
return !getDescriptorsByName(name).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines/checks if the given descriptor may be added to the container.<br>
|
||||
* This implies a check for the capabilities of the container specified by
|
||||
* its {@linkplain #getContainerType() container type}.<br>
|
||||
*
|
||||
* @param descriptor the descriptor to test.
|
||||
* @return <code>true</code> if {@link #addDescriptor(MetadataDescriptor)}
|
||||
* can be called with given descriptor.
|
||||
*/
|
||||
public boolean isAddSupported(final MetadataDescriptor descriptor) {
|
||||
boolean result = getContainerType().checkConstraints(
|
||||
descriptor.getName(), descriptor.getRawData(),
|
||||
descriptor.getType(), descriptor.getStreamNumber(),
|
||||
descriptor.getLanguageIndex()) == null;
|
||||
// Now check if there is already a value contained.
|
||||
if (result && !getContainerType().isMultiValued()) {
|
||||
synchronized (this.perfPoint) {
|
||||
final List<MetadataDescriptor> list = this.descriptors
|
||||
.get(this.perfPoint.setDescriptor(descriptor));
|
||||
if (list != null) {
|
||||
result = list.isEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public final boolean isEmpty() {
|
||||
boolean result = true;
|
||||
if (getDescriptorCount() != 0) {
|
||||
final Iterator<MetadataDescriptor> iterator = getDescriptors()
|
||||
.iterator();
|
||||
while (result && iterator.hasNext()) {
|
||||
result &= iterator.next().isEmpty();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String prettyPrint(final String prefix) {
|
||||
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||
for (final MetadataDescriptor curr : getDescriptors()) {
|
||||
result.append(prefix).append(" |-> ");
|
||||
result.append(curr);
|
||||
result.append(Utils.LINE_SEPARATOR);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all stored descriptors with the given
|
||||
* {@linkplain MetadataDescriptor#getName() name}.<br>
|
||||
*
|
||||
* @param name the name to remove.
|
||||
*/
|
||||
public final void removeDescriptorsByName(final String name) {
|
||||
assert name != null;
|
||||
final Iterator<List<MetadataDescriptor>> iterator = this.descriptors
|
||||
.values().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final List<MetadataDescriptor> curr = iterator.next();
|
||||
if (!curr.isEmpty() && curr.get(0).getName().equals(name)) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@linkplain #assertDescriptor(String) asserts} the existence of a
|
||||
* descriptor with given <code>name</code> and
|
||||
* {@linkplain MetadataDescriptor#setStringValue(String) assings} the string
|
||||
* value.
|
||||
*
|
||||
* @param name the name of the descriptor to set the value for.
|
||||
* @param value the string value.
|
||||
*/
|
||||
protected final void setStringValue(final String name, final String value) {
|
||||
assertDescriptor(name).setStringValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public long writeInto(final OutputStream out) throws IOException {
|
||||
final long chunkSize = getCurrentAsfChunkSize();
|
||||
final List<MetadataDescriptor> descriptorList = getDescriptors();
|
||||
out.write(getGuid().getBytes());
|
||||
Utils.writeUINT64(chunkSize, out);
|
||||
Utils.writeUINT16(descriptorList.size(), out);
|
||||
for (final MetadataDescriptor curr : descriptorList) {
|
||||
curr.writeInto(out, this.containerType);
|
||||
}
|
||||
return chunkSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to uniquely identify an enclosed descriptor by its
|
||||
* name, language index and stream number.<br>
|
||||
* The type of the descriptor is ignored, since it just specifies the data
|
||||
* content.
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
private final static class DescriptorPointer {
|
||||
|
||||
/**
|
||||
* The represented descriptor.
|
||||
*/
|
||||
private MetadataDescriptor desc;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param descriptor the metadata descriptor to identify.
|
||||
*/
|
||||
public DescriptorPointer(final MetadataDescriptor descriptor) {
|
||||
setDescriptor(descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
boolean result = obj == this;
|
||||
if (obj instanceof DescriptorPointer && !result) {
|
||||
final MetadataDescriptor other = ((DescriptorPointer) obj).desc;
|
||||
result = this.desc.getName().equals(other.getName());
|
||||
result &= this.desc.getLanguageIndex() == other
|
||||
.getLanguageIndex();
|
||||
result &= this.desc.getStreamNumber() == other
|
||||
.getStreamNumber();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashCode;
|
||||
hashCode = this.desc.getName().hashCode();
|
||||
hashCode = hashCode * 31 + this.desc.getLanguageIndex();
|
||||
hashCode = hashCode * 31 + this.desc.getStreamNumber();
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the descriptor to identify.
|
||||
*
|
||||
* @param descriptor the descriptor to identify.
|
||||
* @return this instance.
|
||||
*/
|
||||
protected DescriptorPointer setDescriptor(
|
||||
final MetadataDescriptor descriptor) {
|
||||
assert descriptor != null;
|
||||
this.desc = descriptor;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* A factory for creating appropriate {@link MetadataContainer} objects upon
|
||||
* specified {@linkplain ContainerType container types}.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public final class MetadataContainerFactory {
|
||||
|
||||
/**
|
||||
* Factory instance.
|
||||
*/
|
||||
private final static MetadataContainerFactory INSTANCE = new MetadataContainerFactory();
|
||||
|
||||
/**
|
||||
* Hidden utility class constructor.
|
||||
*/
|
||||
private MetadataContainerFactory() {
|
||||
// Hidden
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance.
|
||||
*
|
||||
* @return an instance.
|
||||
*/
|
||||
public static MetadataContainerFactory getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an appropriate {@linkplain MetadataContainer container
|
||||
* implementation} for the given container type.
|
||||
*
|
||||
* @param type the type of container to get a container instance for.
|
||||
* @return appropriate container implementation.
|
||||
*/
|
||||
public MetadataContainer createContainer(final ContainerType type) {
|
||||
return createContainer(type, 0, BigInteger.ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience Method for I/O. Same as
|
||||
* {@link #createContainer(ContainerType)}, but additionally assigns
|
||||
* position and size. (since a {@link MetadataContainer} is actually a
|
||||
* {@link Chunk}).
|
||||
*
|
||||
* @param type The containers type.
|
||||
* @param pos the position within the stream.
|
||||
* @param chunkSize the size of the container.
|
||||
* @return an appropriate container implementation with assigned size and
|
||||
* position.
|
||||
*/
|
||||
public MetadataContainer createContainer(final ContainerType type,
|
||||
final long pos, final BigInteger chunkSize) {
|
||||
MetadataContainer result;
|
||||
if (type == ContainerType.CONTENT_DESCRIPTION) {
|
||||
result = new ContentDescription(pos, chunkSize);
|
||||
} else if (type == ContainerType.CONTENT_BRANDING) {
|
||||
result = new ContentBranding(pos, chunkSize);
|
||||
} else {
|
||||
result = new MetadataContainer(type, pos, chunkSize);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method which calls {@link #createContainer(ContainerType)}
|
||||
* for each given container type.
|
||||
*
|
||||
* @param types types of the container which are to be created.
|
||||
* @return appropriate container implementations.
|
||||
*/
|
||||
public MetadataContainer[] createContainers(final ContainerType[] types) {
|
||||
assert types != null;
|
||||
final MetadataContainer[] result = new MetadataContainer[types.length];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = createContainer(types[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,877 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
import org.jaudiotagger.logging.ErrorMessage;
|
||||
import org.jaudiotagger.tag.TagOptionSingleton;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* This structure represents metadata objects in ASF {@link MetadataContainer}.<br>
|
||||
* The values are
|
||||
* {@linkplain ContainerType#assertConstraints(String, byte[], int, int, int)
|
||||
* checked} against the capability introduced by the given
|
||||
* {@link ContainerType} at construction.<br>
|
||||
* <br>
|
||||
* <b>Limitation</b>: Even though some container types do not restrict the data
|
||||
* size to {@link Integer#MAX_VALUE}, this implementation does it (due to java
|
||||
* nature).<br>
|
||||
* 2 GiB of data should suffice, and even be to large for normal java heap.
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class MetadataDescriptor implements Comparable<MetadataDescriptor>,
|
||||
Cloneable {
|
||||
|
||||
/**
|
||||
* Maximum value for WORD.
|
||||
*/
|
||||
public static final long DWORD_MAXVALUE = new BigInteger("FFFFFFFF", 16)
|
||||
.longValue();
|
||||
/**
|
||||
* The maximum language index allowed. (exclusive)
|
||||
*/
|
||||
public static final int MAX_LANG_INDEX = 127;
|
||||
/**
|
||||
* Maximum stream number. (inclusive)
|
||||
*/
|
||||
public static final int MAX_STREAM_NUMBER = 127;
|
||||
/**
|
||||
* Maximum value for a QWORD value (64 bit unsigned).<br>
|
||||
*/
|
||||
public static final BigInteger QWORD_MAXVALUE = new BigInteger(
|
||||
"FFFFFFFFFFFFFFFF", 16);
|
||||
/**
|
||||
* Constant for the metadata descriptor-type for binary data.
|
||||
*/
|
||||
public final static int TYPE_BINARY = 1;
|
||||
/**
|
||||
* Constant for the metadata descriptor-type for booleans.
|
||||
*/
|
||||
public final static int TYPE_BOOLEAN = 2;
|
||||
/**
|
||||
* Constant for the metadata descriptor-type for DWORD (32-bit unsigned). <br>
|
||||
*/
|
||||
public final static int TYPE_DWORD = 3;
|
||||
/**
|
||||
* Constant for the metadata descriptor-type for GUIDs (128-bit).<br>
|
||||
*/
|
||||
public final static int TYPE_GUID = 6;
|
||||
/**
|
||||
* Constant for the metadata descriptor-type for QWORD (64-bit unsinged). <br>
|
||||
*/
|
||||
public final static int TYPE_QWORD = 4;
|
||||
/**
|
||||
* Constant for the metadata descriptor-type for Strings.
|
||||
*/
|
||||
public final static int TYPE_STRING = 0;
|
||||
/**
|
||||
* Constant for the metadata descriptor-type for WORD (16-bit unsigned). <br>
|
||||
*/
|
||||
public final static int TYPE_WORD = 5;
|
||||
/**
|
||||
* Maximum value for WORD.
|
||||
*/
|
||||
public static final int WORD_MAXVALUE = 65535;
|
||||
/**
|
||||
* Logger instance.
|
||||
*/
|
||||
private static final Logger LOGGER = Logger
|
||||
.getLogger("org.jaudiotagger.audio.asf.data");
|
||||
/**
|
||||
* Stores the containerType of the descriptor.
|
||||
*/
|
||||
private final ContainerType containerType;
|
||||
/**
|
||||
* The name of the metadata descriptor.
|
||||
*/
|
||||
private final String name;
|
||||
/**
|
||||
* The binary representation of the value.
|
||||
*/
|
||||
/*
|
||||
* Note: The maximum data length could be up to a 64-Bit number (unsigned),
|
||||
* but java for now handles just int sized byte[]. Since this class stores
|
||||
* all data in primitive byte[] this size restriction is cascaded to all
|
||||
* dependent implementations.
|
||||
*/
|
||||
private byte[] content = new byte[0];
|
||||
/**
|
||||
* This field shows the type of the metadata descriptor. <br>
|
||||
*
|
||||
* @see #TYPE_BINARY
|
||||
* @see #TYPE_BOOLEAN
|
||||
* @see #TYPE_DWORD
|
||||
* @see #TYPE_GUID
|
||||
* @see #TYPE_QWORD
|
||||
* @see #TYPE_STRING
|
||||
* @see #TYPE_WORD
|
||||
*/
|
||||
private int descriptorType;
|
||||
/**
|
||||
* the index of the language in the {@linkplain LanguageList language list}
|
||||
* this descriptor applies to.<br>
|
||||
*/
|
||||
private int languageIndex = 0;
|
||||
/**
|
||||
* The number of the stream, this descriptor applies to.<br>
|
||||
*/
|
||||
private int streamNumber = 0;
|
||||
|
||||
/**
|
||||
* Creates an Instance.<br>
|
||||
*
|
||||
* @param type the container type, this descriptor is resctricted to.
|
||||
* @param propName Name of the MetadataDescriptor.
|
||||
* @param propType Type of the metadata descriptor. See {@link #descriptorType}
|
||||
*/
|
||||
public MetadataDescriptor(final ContainerType type, final String propName,
|
||||
final int propType) {
|
||||
this(type, propName, propType, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Instance.
|
||||
*
|
||||
* @param type The container type the values (the whole descriptor) is
|
||||
* restricted to.
|
||||
* @param propName Name of the MetadataDescriptor.
|
||||
* @param propType Type of the metadata descriptor. See {@link #descriptorType}
|
||||
* @param stream the number of the stream the descriptor refers to.
|
||||
* @param language the index of the language entry in a {@link LanguageList} this
|
||||
* descriptor refers to.<br>
|
||||
* <b>Consider</b>: No checks performed if language entry exists.
|
||||
*/
|
||||
public MetadataDescriptor(final ContainerType type, final String propName,
|
||||
final int propType, final int stream, final int language) {
|
||||
assert type != null;
|
||||
type.assertConstraints(propName, new byte[0], propType, stream,
|
||||
language);
|
||||
this.containerType = type;
|
||||
this.name = propName;
|
||||
this.descriptorType = propType;
|
||||
this.streamNumber = stream;
|
||||
this.languageIndex = language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.<br>
|
||||
* Capabilities are set to {@link ContainerType#METADATA_LIBRARY_OBJECT}.<br>
|
||||
*
|
||||
* @param propName name of the metadata descriptor.
|
||||
*/
|
||||
public MetadataDescriptor(final String propName) {
|
||||
this(propName, TYPE_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Instance.<br>
|
||||
* Capabilities are set to {@link ContainerType#METADATA_LIBRARY_OBJECT}.<br>
|
||||
*
|
||||
* @param propName Name of the MetadataDescriptor.
|
||||
* @param propType Type of the metadata descriptor. See {@link #descriptorType}
|
||||
*/
|
||||
public MetadataDescriptor(final String propName, final int propType) {
|
||||
this(ContainerType.METADATA_LIBRARY_OBJECT, propName, propType, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the descriptors value into a number if possible.<br>
|
||||
* A boolean will be converted to "1" if <code>true</code>,
|
||||
* otherwise "0".<br>
|
||||
* String will be interpreted as number with radix "10".<br>
|
||||
* Binary data will be interpreted as the default WORD,DWORD or QWORD binary
|
||||
* representation, but only if the data does not exceed 8 bytes. This
|
||||
* precaution is done to prevent creating a number of a multi kilobyte
|
||||
* image.<br>
|
||||
* A GUID cannot be converted in any case.
|
||||
*
|
||||
* @return number representation.
|
||||
* @throws NumberFormatException If no conversion is supported.
|
||||
*/
|
||||
public BigInteger asNumber() {
|
||||
BigInteger result = null;
|
||||
switch (this.descriptorType) {
|
||||
case TYPE_BOOLEAN:
|
||||
case TYPE_WORD:
|
||||
case TYPE_DWORD:
|
||||
case TYPE_QWORD:
|
||||
case TYPE_BINARY:
|
||||
if (this.content.length > 8) {
|
||||
throw new NumberFormatException(
|
||||
"Binary data would exceed QWORD");
|
||||
}
|
||||
break;
|
||||
case TYPE_GUID:
|
||||
throw new NumberFormatException(
|
||||
"GUID cannot be converted to a number.");
|
||||
case TYPE_STRING:
|
||||
result = new BigInteger(getString(), 10);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (result == null) {
|
||||
final byte[] copy = new byte[this.content.length];
|
||||
for (int i = 0; i < copy.length; i++) {
|
||||
copy[i] = this.content[this.content.length - (i + 1)];
|
||||
}
|
||||
result = new BigInteger(1, copy);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see java.lang.Object#clone()
|
||||
*/
|
||||
@Override
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
return super.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public int compareTo(final MetadataDescriptor other) {
|
||||
return getName().compareTo(other.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates a copy of the current object. <br>
|
||||
* All data will be copied, too. <br>
|
||||
*
|
||||
* @return A new metadata descriptor containing the same values as the
|
||||
* current one.
|
||||
*/
|
||||
public MetadataDescriptor createCopy() {
|
||||
final MetadataDescriptor result = new MetadataDescriptor(
|
||||
this.containerType, this.name, this.descriptorType,
|
||||
this.streamNumber, this.languageIndex);
|
||||
result.content = getRawData();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
boolean result = false;
|
||||
if (obj instanceof MetadataDescriptor) {
|
||||
if (obj == this) {
|
||||
result = true;
|
||||
} else {
|
||||
final MetadataDescriptor other = (MetadataDescriptor) obj;
|
||||
result = other.getName().equals(getName())
|
||||
&& other.descriptorType == this.descriptorType
|
||||
&& other.languageIndex == this.languageIndex
|
||||
&& other.streamNumber == this.streamNumber
|
||||
&& Arrays.equals(this.content, other.content);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the MetadataDescriptor as a Boolean. <br>
|
||||
* If no Conversion is Possible false is returned. <br>
|
||||
* <code>true</code> if first byte of {@link #content}is not zero.
|
||||
*
|
||||
* @return boolean representation of the current value.
|
||||
*/
|
||||
public boolean getBoolean() {
|
||||
return this.content.length > 0 && this.content[0] != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will return a byte array, which can directly be written into
|
||||
* an "Extended Content Description"-chunk. <br>
|
||||
*
|
||||
* @return byte[] with the data, that occurs in ASF files.
|
||||
* @deprecated {@link #writeInto(OutputStream, ContainerType)} is used
|
||||
*/
|
||||
@Deprecated
|
||||
public byte[] getBytes() {
|
||||
final ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
try {
|
||||
writeInto(result, this.containerType);
|
||||
} catch (final IOException e) {
|
||||
LOGGER.warning(e.getMessage());
|
||||
}
|
||||
return result.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the container type this descriptor ist restricted to.
|
||||
*
|
||||
* @return the container type
|
||||
*/
|
||||
public ContainerType getContainerType() {
|
||||
return this.containerType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size (in bytes) this descriptor will take when written to an
|
||||
* ASF file.<br>
|
||||
*
|
||||
* @param type the container type for which the size is calculated.
|
||||
* @return size of the descriptor in an ASF file.
|
||||
*/
|
||||
public int getCurrentAsfSize(final ContainerType type) {
|
||||
/*
|
||||
* 2 bytes name length, 2 bytes name zero term, 2 bytes type, 2 bytes
|
||||
* content length
|
||||
*/
|
||||
int result = 8;
|
||||
|
||||
if (type != ContainerType.EXTENDED_CONTENT) {
|
||||
// Stream number and language index (respectively reserved field).
|
||||
// And +2 bytes, because data type is 32 bit, not 16
|
||||
result += 6;
|
||||
}
|
||||
result += getName().length() * 2;
|
||||
|
||||
if (this.getType() == TYPE_BOOLEAN) {
|
||||
result += 2;
|
||||
if (type == ContainerType.EXTENDED_CONTENT) {
|
||||
// Extended content description boolean values are stored with
|
||||
// 32-bit
|
||||
result += 2;
|
||||
}
|
||||
} else {
|
||||
|
||||
result += this.content.length;
|
||||
if (TYPE_STRING == this.getType()) {
|
||||
result += 2; // zero term of content string.
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the GUID value, if content could represent one.
|
||||
*
|
||||
* @return GUID value
|
||||
*/
|
||||
public GUID getGuid() {
|
||||
GUID result = null;
|
||||
if (getType() == TYPE_GUID && this.content.length == GUID.GUID_LENGTH) {
|
||||
result = new GUID(this.content);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the language that is referred (see
|
||||
* {@link LanguageList}):
|
||||
*
|
||||
* @return the language index
|
||||
*/
|
||||
public int getLanguageIndex() {
|
||||
return this.languageIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index of the referred language (see {@link LanguageList}).<br>
|
||||
* <b>Consider</b>: The {@linkplain #containerType requirements} must be
|
||||
* held.
|
||||
*
|
||||
* @param language the language index to set
|
||||
*/
|
||||
public void setLanguageIndex(final int language) {
|
||||
this.containerType.assertConstraints(this.name, this.content,
|
||||
this.descriptorType, this.streamNumber, language);
|
||||
this.languageIndex = language;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the name of the metadata descriptor.
|
||||
*
|
||||
* @return Name.
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the value of the metadata descriptor as a long. <br>
|
||||
* Converts the needed amount of byte out of {@link #content}to a number. <br>
|
||||
* Only possible if {@link #getType()}equals on of the following: <br>
|
||||
* <li>
|
||||
*
|
||||
* @return integer value.
|
||||
* @see #TYPE_BOOLEAN </li> <li>
|
||||
* @see #TYPE_DWORD </li> <li>
|
||||
* @see #TYPE_QWORD </li> <li>
|
||||
* @see #TYPE_WORD </li>
|
||||
*/
|
||||
public long getNumber() {
|
||||
int bytesNeeded;
|
||||
switch (getType()) {
|
||||
case TYPE_BOOLEAN:
|
||||
bytesNeeded = 1;
|
||||
break;
|
||||
case TYPE_DWORD:
|
||||
bytesNeeded = 4;
|
||||
break;
|
||||
case TYPE_QWORD:
|
||||
bytesNeeded = 8;
|
||||
break;
|
||||
case TYPE_WORD:
|
||||
bytesNeeded = 2;
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException(
|
||||
"The current type doesn't allow an interpretation as a number. ("
|
||||
+ getType() + ")");
|
||||
}
|
||||
if (bytesNeeded > this.content.length) {
|
||||
throw new IllegalStateException(
|
||||
"The stored data cannot represent the type of current object.");
|
||||
}
|
||||
long result = 0;
|
||||
for (int i = 0; i < bytesNeeded; i++) {
|
||||
result |= (((long) this.content[i] & 0xFF) << (i * 8));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a copy of the content of the descriptor. <br>
|
||||
*
|
||||
* @return The content in binary representation, as it would be written to
|
||||
* asf file. <br>
|
||||
*/
|
||||
public byte[] getRawData() {
|
||||
final byte[] copy = new byte[this.content.length];
|
||||
System.arraycopy(this.content, 0, copy, 0, this.content.length);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size (in bytes) the binary representation of the content
|
||||
* uses. (length of {@link #getRawData()})<br>
|
||||
*
|
||||
* @return size of binary representation of the content.
|
||||
*/
|
||||
public int getRawDataSize() {
|
||||
return this.content.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stream number this descriptor applies to.<br>
|
||||
*
|
||||
* @return the stream number.
|
||||
*/
|
||||
public int getStreamNumber() {
|
||||
return this.streamNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the stream number the descriptor applies to.<br>
|
||||
* <b>Consider</b>: The {@linkplain #containerType requirements} must be
|
||||
* held.
|
||||
*
|
||||
* @param stream the stream number to set
|
||||
*/
|
||||
public void setStreamNumber(final int stream) {
|
||||
this.containerType.assertConstraints(this.name, this.content,
|
||||
this.descriptorType, stream, this.languageIndex);
|
||||
this.streamNumber = stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the MetadataDescriptor as a String. <br>
|
||||
*
|
||||
* @return String - Representation Value
|
||||
*/
|
||||
public String getString() {
|
||||
String result = null;
|
||||
switch (getType()) {
|
||||
case TYPE_BINARY:
|
||||
result = "binary data";
|
||||
break;
|
||||
case TYPE_BOOLEAN:
|
||||
result = String.valueOf(getBoolean());
|
||||
break;
|
||||
case TYPE_GUID:
|
||||
result = getGuid() == null ? "Invalid GUID" : getGuid().toString();
|
||||
break;
|
||||
case TYPE_QWORD:
|
||||
case TYPE_DWORD:
|
||||
case TYPE_WORD:
|
||||
result = String.valueOf(getNumber());
|
||||
break;
|
||||
case TYPE_STRING:
|
||||
try {
|
||||
result = new String(this.content, "UTF-16LE");
|
||||
} catch (final UnsupportedEncodingException e) {
|
||||
LOGGER.warning(e.getMessage());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Current type is not known.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts the given string value into the current
|
||||
* {@linkplain #getType() data type}.
|
||||
*
|
||||
* @param value value to set.
|
||||
* @throws IllegalArgumentException If conversion was impossible.
|
||||
*/
|
||||
public void setString(final String value)
|
||||
throws IllegalArgumentException {
|
||||
try {
|
||||
switch (getType()) {
|
||||
case TYPE_BINARY:
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot interpret binary as string.");
|
||||
case TYPE_BOOLEAN:
|
||||
setBooleanValue(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case TYPE_DWORD:
|
||||
setDWordValue(Long.parseLong(value));
|
||||
break;
|
||||
case TYPE_QWORD:
|
||||
setQWordValue(new BigInteger(value, 10));
|
||||
break;
|
||||
case TYPE_WORD:
|
||||
setWordValue(Integer.parseInt(value));
|
||||
break;
|
||||
case TYPE_GUID:
|
||||
setGUIDValue(GUID.parseGUID(value));
|
||||
break;
|
||||
case TYPE_STRING:
|
||||
setStringValue(value);
|
||||
break;
|
||||
default:
|
||||
// new Type added but not handled.
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
} catch (final NumberFormatException nfe) {
|
||||
throw new IllegalArgumentException(
|
||||
"Value cannot be parsed as Number or is out of range (\""
|
||||
+ value + "\")", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the metadata descriptor. <br>
|
||||
*
|
||||
* @return the value of {@link #descriptorType}
|
||||
* @see #TYPE_BINARY
|
||||
* @see #TYPE_BOOLEAN
|
||||
* @see #TYPE_DWORD
|
||||
* @see #TYPE_GUID
|
||||
* @see #TYPE_QWORD
|
||||
* @see #TYPE_STRING
|
||||
* @see #TYPE_WORD
|
||||
*/
|
||||
public int getType() {
|
||||
return this.descriptorType;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.name.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if the binary data is empty. <br>
|
||||
* Disregarding the type of the descriptor its content is stored as a byte
|
||||
* array.
|
||||
*
|
||||
* @return <code>true</code> if no value is set.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return this.content.length == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Value of the current metadata descriptor. <br>
|
||||
* Using this method will change {@link #descriptorType}to
|
||||
* {@link #TYPE_BINARY}.<br>
|
||||
*
|
||||
* @param data Value to set.
|
||||
* @throws IllegalArgumentException if data is invalid for {@linkplain #getContainerType()
|
||||
* container}.
|
||||
*/
|
||||
public void setBinaryValue(final byte[] data)
|
||||
throws IllegalArgumentException {
|
||||
this.containerType.assertConstraints(this.name, data,
|
||||
this.descriptorType, this.streamNumber, this.languageIndex);
|
||||
this.content = data.clone();
|
||||
this.descriptorType = TYPE_BINARY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Value of the current metadata descriptor. <br>
|
||||
* Using this method will change {@link #descriptorType}to
|
||||
* {@link #TYPE_BOOLEAN}.<br>
|
||||
*
|
||||
* @param value Value to set.
|
||||
*/
|
||||
public void setBooleanValue(final boolean value) {
|
||||
this.content = new byte[]{value ? (byte) 1 : 0};
|
||||
this.descriptorType = TYPE_BOOLEAN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Value of the current metadata descriptor. <br>
|
||||
* Using this method will change {@link #descriptorType}to
|
||||
* {@link #TYPE_DWORD}.
|
||||
*
|
||||
* @param value Value to set.
|
||||
*/
|
||||
public void setDWordValue(final long value) {
|
||||
if (value < 0 || value > DWORD_MAXVALUE) {
|
||||
throw new IllegalArgumentException("value out of range (0-"
|
||||
+ DWORD_MAXVALUE + ")");
|
||||
}
|
||||
this.content = Utils.getBytes(value, 4);
|
||||
this.descriptorType = TYPE_DWORD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the metadata descriptor.<br>
|
||||
* Using this method will change {@link #descriptorType} to
|
||||
* {@link #TYPE_GUID}
|
||||
*
|
||||
* @param value value to set.
|
||||
*/
|
||||
public void setGUIDValue(final GUID value) {
|
||||
this.containerType.assertConstraints(this.name, value.getBytes(),
|
||||
TYPE_GUID, this.streamNumber, this.languageIndex);
|
||||
this.content = value.getBytes();
|
||||
this.descriptorType = TYPE_GUID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Value of the current metadata descriptor. <br>
|
||||
* Using this method will change {@link #descriptorType}to
|
||||
* {@link #TYPE_QWORD}
|
||||
*
|
||||
* @param value Value to set.
|
||||
* @throws NumberFormatException on <code>null</code> values.
|
||||
* @throws IllegalArgumentException on illegal values or values exceeding range.
|
||||
*/
|
||||
public void setQWordValue(final BigInteger value)
|
||||
throws IllegalArgumentException {
|
||||
if (value == null) {
|
||||
throw new NumberFormatException("null");
|
||||
}
|
||||
if (BigInteger.ZERO.compareTo(value) > 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Only unsigned values allowed (no negative)");
|
||||
}
|
||||
if (MetadataDescriptor.QWORD_MAXVALUE.compareTo(value) < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Value exceeds QWORD (64 bit unsigned)");
|
||||
}
|
||||
this.content = new byte[8];
|
||||
final byte[] valuesBytes = value.toByteArray();
|
||||
if (valuesBytes.length <= 8) {
|
||||
for (int i = valuesBytes.length - 1; i >= 0; i--) {
|
||||
this.content[valuesBytes.length - (i + 1)] = valuesBytes[i];
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* In case of 64-Bit set
|
||||
*/
|
||||
Arrays.fill(this.content, (byte) 0xFF);
|
||||
}
|
||||
this.descriptorType = TYPE_QWORD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Value of the current metadata descriptor. <br>
|
||||
* Using this method will change {@link #descriptorType}to
|
||||
* {@link #TYPE_QWORD}
|
||||
*
|
||||
* @param value Value to set.
|
||||
*/
|
||||
public void setQWordValue(final long value) {
|
||||
if (value < 0) {
|
||||
throw new IllegalArgumentException("value out of range (0-"
|
||||
+ MetadataDescriptor.QWORD_MAXVALUE.toString() + ")");
|
||||
}
|
||||
this.content = Utils.getBytes(value, 8);
|
||||
this.descriptorType = TYPE_QWORD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Value of the current metadata descriptor. <br>
|
||||
* Using this method will change {@link #descriptorType}to
|
||||
* {@link #TYPE_STRING}.
|
||||
*
|
||||
* @param value Value to set.
|
||||
* @throws IllegalArgumentException If byte representation would take more than 65535 Bytes.
|
||||
*/
|
||||
// TODO Test
|
||||
public void setStringValue(final String value)
|
||||
throws IllegalArgumentException {
|
||||
if (value == null) {
|
||||
this.content = new byte[0];
|
||||
} else {
|
||||
final byte[] tmp = Utils.getBytes(value, AsfHeader.ASF_CHARSET);
|
||||
if (getContainerType().isWithinValueRange(tmp.length)) {
|
||||
// Everything is fine here, data can be stored.
|
||||
this.content = tmp;
|
||||
} else {
|
||||
// Normally a size violation, check if JAudiotagger my truncate
|
||||
// the string
|
||||
if (TagOptionSingleton.getInstance()
|
||||
.isTruncateTextWithoutErrors()) {
|
||||
// truncate the string
|
||||
final int copyBytes = (int) getContainerType()
|
||||
.getMaximumDataLength().longValue();
|
||||
this.content = new byte[copyBytes % 2 == 0 ? copyBytes
|
||||
: copyBytes - 1];
|
||||
System.arraycopy(tmp, 0, this.content, 0,
|
||||
this.content.length);
|
||||
} else {
|
||||
// We may not truncate, so its an error
|
||||
throw new IllegalArgumentException(
|
||||
ErrorMessage.WMA_LENGTH_OF_DATA_IS_TOO_LARGE
|
||||
.getMsg(tmp.length, getContainerType()
|
||||
.getMaximumDataLength(),
|
||||
getContainerType()
|
||||
.getContainerGUID()
|
||||
.getDescription()));
|
||||
}
|
||||
}
|
||||
}
|
||||
this.descriptorType = TYPE_STRING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Value of the current metadata descriptor. <br>
|
||||
* Using this method will change {@link #descriptorType}to
|
||||
* {@link #TYPE_WORD}
|
||||
*
|
||||
* @param value Value to set.
|
||||
* @throws IllegalArgumentException on negative values. ASF just supports unsigned values.
|
||||
*/
|
||||
public void setWordValue(final int value)
|
||||
throws IllegalArgumentException {
|
||||
if (value < 0 || value > WORD_MAXVALUE) {
|
||||
throw new IllegalArgumentException("value out of range (0-"
|
||||
+ WORD_MAXVALUE + ")");
|
||||
}
|
||||
this.content = Utils.getBytes(value, 2);
|
||||
this.descriptorType = TYPE_WORD;
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName()
|
||||
+ " : "
|
||||
+ new String[]{"String: ", "Binary: ", "Boolean: ",
|
||||
"DWORD: ", "QWORD:", "WORD:", "GUID:"}[this.descriptorType]
|
||||
+ getString() + " (language: " + this.languageIndex
|
||||
+ " / stream: " + this.streamNumber + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes this descriptor into the specified output stream.<br>
|
||||
*
|
||||
* @param out stream to write into.
|
||||
* @param contType the container type this descriptor is written to.
|
||||
* @return amount of bytes written.
|
||||
* @throws IOException on I/O Errors
|
||||
*/
|
||||
public int writeInto(final OutputStream out,
|
||||
final ContainerType contType) throws IOException {
|
||||
final int size = getCurrentAsfSize(contType);
|
||||
/*
|
||||
* Booleans are stored as one byte, if a boolean is written, the data
|
||||
* must be converted according to the container type.
|
||||
*/
|
||||
byte[] binaryData;
|
||||
if (this.descriptorType == TYPE_BOOLEAN) {
|
||||
binaryData = new byte[contType == ContainerType.EXTENDED_CONTENT ? 4
|
||||
: 2];
|
||||
binaryData[0] = (byte) (getBoolean() ? 1 : 0);
|
||||
} else {
|
||||
binaryData = this.content;
|
||||
}
|
||||
// for Metadata objects the stream number and language index
|
||||
if (contType != ContainerType.EXTENDED_CONTENT) {
|
||||
Utils.writeUINT16(getLanguageIndex(), out);
|
||||
Utils.writeUINT16(getStreamNumber(), out);
|
||||
}
|
||||
Utils.writeUINT16(getName().length() * 2 + 2, out);
|
||||
|
||||
// The name for the metadata objects come later
|
||||
if (contType == ContainerType.EXTENDED_CONTENT) {
|
||||
out.write(Utils.getBytes(getName(), AsfHeader.ASF_CHARSET));
|
||||
out.write(AsfHeader.ZERO_TERM);
|
||||
}
|
||||
|
||||
// type and content len follow up are identical
|
||||
final int type = getType();
|
||||
Utils.writeUINT16(type, out);
|
||||
int contentLen = binaryData.length;
|
||||
if (TYPE_STRING == type) {
|
||||
contentLen += 2; // Zero Term
|
||||
}
|
||||
|
||||
if (contType == ContainerType.EXTENDED_CONTENT) {
|
||||
Utils.writeUINT16(contentLen, out);
|
||||
} else {
|
||||
Utils.writeUINT32(contentLen, out);
|
||||
}
|
||||
|
||||
// Metadata objects now write their descriptor name
|
||||
if (contType != ContainerType.EXTENDED_CONTENT) {
|
||||
out.write(Utils.getBytes(getName(), AsfHeader.ASF_CHARSET));
|
||||
out.write(AsfHeader.ZERO_TERM);
|
||||
}
|
||||
|
||||
// The content.
|
||||
out.write(binaryData);
|
||||
if (TYPE_STRING == type) {
|
||||
out.write(AsfHeader.ZERO_TERM);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class represents the "Stream Bitrate Properties" chunk of an ASF media
|
||||
* file. <br>
|
||||
* It is optional, but contains useful information about the streams bitrate.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class StreamBitratePropertiesChunk extends Chunk {
|
||||
|
||||
/**
|
||||
* For each call of {@link #addBitrateRecord(int, long)} an {@link Long}
|
||||
* object is appended, which represents the average bitrate.
|
||||
*/
|
||||
private final List<Long> bitRates;
|
||||
|
||||
/**
|
||||
* For each call of {@link #addBitrateRecord(int, long)} an {@link Integer}
|
||||
* object is appended, which represents the stream-number.
|
||||
*/
|
||||
private final List<Integer> streamNumbers;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param chunkLen Length of current chunk.
|
||||
*/
|
||||
public StreamBitratePropertiesChunk(final BigInteger chunkLen) {
|
||||
super(GUID.GUID_STREAM_BITRATE_PROPERTIES, chunkLen);
|
||||
this.bitRates = new ArrayList<Long>();
|
||||
this.streamNumbers = new ArrayList<Integer>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the public values of a stream-record.
|
||||
*
|
||||
* @param streamNum The number of the referred stream.
|
||||
* @param averageBitrate Its average bitrate.
|
||||
*/
|
||||
public void addBitrateRecord(final int streamNum, final long averageBitrate) {
|
||||
this.streamNumbers.add(streamNum);
|
||||
this.bitRates.add(averageBitrate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the average bitrate of the given stream.<br>
|
||||
*
|
||||
* @param streamNumber Number of the stream whose bitrate to determine.
|
||||
* @return The average bitrate of the numbered stream. <code>-1</code> if no
|
||||
* information was given.
|
||||
*/
|
||||
public long getAvgBitrate(final int streamNumber) {
|
||||
final Integer seach = streamNumber;
|
||||
final int index = this.streamNumbers.indexOf(seach);
|
||||
long result;
|
||||
if (index == -1) {
|
||||
result = -1;
|
||||
} else {
|
||||
result = this.bitRates.get(index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see org.jaudiotagger.audio.asf.data.Chunk#prettyPrint(String)
|
||||
*/
|
||||
@Override
|
||||
public String prettyPrint(final String prefix) {
|
||||
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||
for (int i = 0; i < this.bitRates.size(); i++) {
|
||||
result.append(prefix).append(" |-> Stream no. \"").append(
|
||||
this.streamNumbers.get(i)).append(
|
||||
"\" has an average bitrate of \"").append(
|
||||
this.bitRates.get(i)).append('"').append(
|
||||
Utils.LINE_SEPARATOR);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,180 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* This class is the base for all handled stream contents. <br>
|
||||
* A Stream chunk delivers information about a audio or video stream. Because of
|
||||
* this the stream chunk identifies in one field what type of stream it is
|
||||
* describing and so other data is provided. However some information is common
|
||||
* to all stream chunks which are stored in this hierarchy of the class tree.
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public abstract class StreamChunk extends Chunk {
|
||||
|
||||
/**
|
||||
* Stores the stream type.<br>
|
||||
*
|
||||
* @see GUID#GUID_AUDIOSTREAM
|
||||
* @see GUID#GUID_VIDEOSTREAM
|
||||
*/
|
||||
private final GUID type;
|
||||
/**
|
||||
* If <code>true</code>, the stream data is encrypted.
|
||||
*/
|
||||
private boolean contentEncrypted;
|
||||
/**
|
||||
* This field stores the number of the current stream. <br>
|
||||
*/
|
||||
private int streamNumber;
|
||||
/**
|
||||
* @see #typeSpecificDataSize
|
||||
*/
|
||||
private long streamSpecificDataSize;
|
||||
/**
|
||||
* Something technical. <br>
|
||||
* Format time in 100-ns steps.
|
||||
*/
|
||||
private long timeOffset;
|
||||
/**
|
||||
* Stores the size of type specific data structure within chunk.
|
||||
*/
|
||||
private long typeSpecificDataSize;
|
||||
|
||||
/**
|
||||
* Creates an instance
|
||||
*
|
||||
* @param streamType The GUID which tells the stream type represented (
|
||||
* {@link GUID#GUID_AUDIOSTREAM} or {@link GUID#GUID_VIDEOSTREAM}
|
||||
* ):
|
||||
* @param chunkLen length of chunk
|
||||
*/
|
||||
public StreamChunk(final GUID streamType, final BigInteger chunkLen) {
|
||||
super(GUID.GUID_STREAM, chunkLen);
|
||||
assert GUID.GUID_AUDIOSTREAM.equals(streamType)
|
||||
|| GUID.GUID_VIDEOSTREAM.equals(streamType);
|
||||
this.type = streamType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the streamNumber.
|
||||
*/
|
||||
public int getStreamNumber() {
|
||||
return this.streamNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param streamNum The streamNumber to set.
|
||||
*/
|
||||
public void setStreamNumber(final int streamNum) {
|
||||
this.streamNumber = streamNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the streamSpecificDataSize.
|
||||
*/
|
||||
public long getStreamSpecificDataSize() {
|
||||
return this.streamSpecificDataSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param strSpecDataSize The streamSpecificDataSize to set.
|
||||
*/
|
||||
public void setStreamSpecificDataSize(final long strSpecDataSize) {
|
||||
this.streamSpecificDataSize = strSpecDataSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stream type of the stream chunk.<br>
|
||||
*
|
||||
* @return {@link GUID#GUID_AUDIOSTREAM} or {@link GUID#GUID_VIDEOSTREAM}.
|
||||
*/
|
||||
public GUID getStreamType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the timeOffset.
|
||||
*/
|
||||
public long getTimeOffset() {
|
||||
return this.timeOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timeOffs sets the time offset
|
||||
*/
|
||||
public void setTimeOffset(final long timeOffs) {
|
||||
this.timeOffset = timeOffs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the typeSpecificDataSize.
|
||||
*/
|
||||
public long getTypeSpecificDataSize() {
|
||||
return this.typeSpecificDataSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param typeSpecDataSize The typeSpecificDataSize to set.
|
||||
*/
|
||||
public void setTypeSpecificDataSize(final long typeSpecDataSize) {
|
||||
this.typeSpecificDataSize = typeSpecDataSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the contentEncrypted.
|
||||
*/
|
||||
public boolean isContentEncrypted() {
|
||||
return this.contentEncrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cntEnc The contentEncrypted to set.
|
||||
*/
|
||||
public void setContentEncrypted(final boolean cntEnc) {
|
||||
this.contentEncrypted = cntEnc;
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see org.jaudiotagger.audio.asf.data.Chunk#prettyPrint(String)
|
||||
*/
|
||||
@Override
|
||||
public String prettyPrint(final String prefix) {
|
||||
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||
result.append(prefix).append(" |-> Stream number: ").append(
|
||||
getStreamNumber()).append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" |-> Type specific data size : ")
|
||||
.append(getTypeSpecificDataSize()).append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" |-> Stream specific data size: ")
|
||||
.append(getStreamSpecificDataSize()).append(
|
||||
Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" |-> Time Offset : ")
|
||||
.append(getTimeOffset()).append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" |-> Content Encryption : ")
|
||||
.append(isContentEncrypted()).append(Utils.LINE_SEPARATOR);
|
||||
return result.toString();
|
||||
}
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.data;
|
||||
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class VideoStreamChunk extends StreamChunk {
|
||||
|
||||
/**
|
||||
* Stores the codecs id. Normally the Four-CC (4-Bytes).
|
||||
*/
|
||||
private byte[] codecId = new byte[0];
|
||||
|
||||
/**
|
||||
* This field stores the height of the video stream.
|
||||
*/
|
||||
private long pictureHeight;
|
||||
|
||||
/**
|
||||
* This field stores the width of the video stream.
|
||||
*/
|
||||
private long pictureWidth;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param chunkLen Length of the entire chunk (including guid and size)
|
||||
*/
|
||||
public VideoStreamChunk(final BigInteger chunkLen) {
|
||||
super(GUID.GUID_VIDEOSTREAM, chunkLen);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the codecId.
|
||||
*/
|
||||
public byte[] getCodecId() {
|
||||
return this.codecId.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param codecIdentifier The codecId to set.
|
||||
*/
|
||||
public void setCodecId(final byte[] codecIdentifier) {
|
||||
this.codecId = codecIdentifier.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link #getCodecId()}, as a String, where each byte has been
|
||||
* converted to a <code>char</code>.
|
||||
*
|
||||
* @return Codec Id as String.
|
||||
*/
|
||||
public String getCodecIdAsString() {
|
||||
String result;
|
||||
if (this.codecId == null) {
|
||||
result = "Unknown";
|
||||
} else {
|
||||
result = new String(getCodecId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the pictureHeight.
|
||||
*/
|
||||
public long getPictureHeight() {
|
||||
return this.pictureHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param picHeight
|
||||
*/
|
||||
public void setPictureHeight(final long picHeight) {
|
||||
this.pictureHeight = picHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the pictureWidth.
|
||||
*/
|
||||
public long getPictureWidth() {
|
||||
return this.pictureWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param picWidth
|
||||
*/
|
||||
public void setPictureWidth(final long picWidth) {
|
||||
this.pictureWidth = picWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see org.jaudiotagger.audio.asf.data.StreamChunk#prettyPrint(String)
|
||||
*/
|
||||
@Override
|
||||
public String prettyPrint(final String prefix) {
|
||||
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
|
||||
result.insert(0, Utils.LINE_SEPARATOR + prefix + "|->VideoStream");
|
||||
result.append(prefix).append("Video info:")
|
||||
.append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" |->Width : ").append(
|
||||
getPictureWidth()).append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" |->Heigth : ").append(
|
||||
getPictureHeight()).append(Utils.LINE_SEPARATOR);
|
||||
result.append(prefix).append(" |->Codec : ").append(
|
||||
getCodecIdAsString()).append(Utils.LINE_SEPARATOR);
|
||||
return result.toString();
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
|
||||
|
||||
<html long="en">
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Classes for data components of the Microsoft Advanced Systems Format header.
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
<!-- package.html by Gary McGath -->
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,135 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This modifier manipulates an ASF header extension object.
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class AsfExtHeaderModifier implements ChunkModifier {
|
||||
|
||||
/**
|
||||
* List of modifiers which are to be applied to contained chunks.
|
||||
*/
|
||||
private final List<ChunkModifier> modifierList;
|
||||
|
||||
/**
|
||||
* Creates an instance.<br>
|
||||
*
|
||||
* @param modifiers modifiers to apply.
|
||||
*/
|
||||
public AsfExtHeaderModifier(final List<ChunkModifier> modifiers) {
|
||||
assert modifiers != null;
|
||||
this.modifierList = new ArrayList<ChunkModifier>(modifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply copies a chunk from <code>source</code> to
|
||||
* <code>destination</code>.<br>
|
||||
* The method assumes, that the GUID has already been read and will write
|
||||
* the provided one to the destination.<br>
|
||||
* The chunk length however will be read and used to determine the amount of
|
||||
* bytes to copy.
|
||||
*
|
||||
* @param guid GUID of the current CHUNK.
|
||||
* @param source source of an ASF chunk, which is to be located at the chunk
|
||||
* length field.
|
||||
* @param destination the destination to copy the chunk to.
|
||||
* @throws IOException on I/O errors.
|
||||
*/
|
||||
private void copyChunk(final GUID guid, final InputStream source,
|
||||
final OutputStream destination) throws IOException {
|
||||
final long chunkSize = Utils.readUINT64(source);
|
||||
destination.write(guid.getBytes());
|
||||
Utils.writeUINT64(chunkSize, destination);
|
||||
Utils.copy(source, destination, chunkSize - 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean isApplicable(final GUID guid) {
|
||||
return GUID.GUID_HEADER_EXTENSION.equals(guid);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public ModificationResult modify(final GUID guid, final InputStream source,
|
||||
final OutputStream destination) throws IOException {
|
||||
assert GUID.GUID_HEADER_EXTENSION.equals(guid);
|
||||
|
||||
long difference = 0;
|
||||
final List<ChunkModifier> modders = new ArrayList<ChunkModifier>(
|
||||
this.modifierList);
|
||||
final Set<GUID> occuredGuids = new HashSet<GUID>();
|
||||
occuredGuids.add(guid);
|
||||
|
||||
final BigInteger chunkLen = Utils.readBig64(source);
|
||||
final GUID reserved1 = Utils.readGUID(source);
|
||||
final int reserved2 = Utils.readUINT16(source);
|
||||
final long dataSize = Utils.readUINT32(source);
|
||||
|
||||
assert dataSize == 0 || dataSize >= 24;
|
||||
assert chunkLen.subtract(BigInteger.valueOf(46)).longValue() == dataSize;
|
||||
|
||||
/*
|
||||
* Stream buffer for the chunk list
|
||||
*/
|
||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
/*
|
||||
* Stream which counts read bytes. Dirty but quick way of implementing
|
||||
* this.
|
||||
*/
|
||||
final CountingInputStream cis = new CountingInputStream(source);
|
||||
|
||||
while (cis.getReadCount() < dataSize) {
|
||||
// read GUID
|
||||
final GUID curr = Utils.readGUID(cis);
|
||||
boolean handled = false;
|
||||
for (int i = 0; i < modders.size() && !handled; i++) {
|
||||
if (modders.get(i).isApplicable(curr)) {
|
||||
final ModificationResult modRes = modders.get(i).modify(
|
||||
curr, cis, bos);
|
||||
difference += modRes.getByteDifference();
|
||||
occuredGuids.addAll(modRes.getOccuredGUIDs());
|
||||
modders.remove(i);
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
if (!handled) {
|
||||
occuredGuids.add(curr);
|
||||
copyChunk(curr, cis, bos);
|
||||
}
|
||||
}
|
||||
// Now apply the left modifiers.
|
||||
for (final ChunkModifier curr : modders) {
|
||||
// chunks, which were not in the source file, will be added to the
|
||||
// destination
|
||||
final ModificationResult result = curr.modify(null, null, bos);
|
||||
difference += result.getByteDifference();
|
||||
occuredGuids.addAll(result.getOccuredGUIDs());
|
||||
}
|
||||
destination.write(GUID.GUID_HEADER_EXTENSION.getBytes());
|
||||
Utils.writeUINT64(chunkLen.add(BigInteger.valueOf(difference))
|
||||
.longValue(), destination);
|
||||
destination.write(reserved1.getBytes());
|
||||
Utils.writeUINT16(reserved2, destination);
|
||||
Utils.writeUINT32(dataSize + difference, destination);
|
||||
destination.write(bos.toByteArray());
|
||||
return new ModificationResult(0, difference, occuredGuids);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.AsfExtendedHeader;
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This reader reads an ASF header extension object from an {@link InputStream}
|
||||
* and creates an {@link AsfExtendedHeader} object.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class AsfExtHeaderReader extends ChunkContainerReader<AsfExtendedHeader> {
|
||||
|
||||
/**
|
||||
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||
*/
|
||||
private final static GUID[] APPLYING = {GUID.GUID_HEADER_EXTENSION};
|
||||
|
||||
/**
|
||||
* Creates a reader instance, which only utilizes the given list of chunk
|
||||
* readers.<br>
|
||||
*
|
||||
* @param toRegister List of {@link ChunkReader} class instances, which are to be
|
||||
* utilized by the instance.
|
||||
* @param readChunkOnce if <code>true</code>, each chunk type (identified by chunk
|
||||
* GUID) will handled only once, if a reader is available, other
|
||||
* chunks will be discarded.
|
||||
*/
|
||||
public AsfExtHeaderReader(
|
||||
final List<Class<? extends ChunkReader>> toRegister,
|
||||
final boolean readChunkOnce) {
|
||||
super(toRegister, readChunkOnce);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean canFail() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected AsfExtendedHeader createContainer(final long streamPosition,
|
||||
final BigInteger chunkLength, final InputStream stream)
|
||||
throws IOException {
|
||||
Utils.readGUID(stream); // First reserved field (should be a specific
|
||||
// GUID.
|
||||
Utils.readUINT16(stream); // Second reserved field (should always be 6)
|
||||
final long extensionSize = Utils.readUINT32(stream);
|
||||
assert extensionSize == 0 || extensionSize >= 24;
|
||||
assert chunkLength.subtract(BigInteger.valueOf(46)).longValue() == extensionSize;
|
||||
return new AsfExtendedHeader(streamPosition, chunkLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public GUID[] getApplyingIds() {
|
||||
return APPLYING.clone();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,231 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.AsfHeader;
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.*;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This <i>class </i> reads an ASF header out of an input stream an creates an
|
||||
* {@link org.jaudiotagger.audio.asf.data.AsfHeader} object if successful. <br>
|
||||
* For now only ASF ver 1.0 is supported, because ver 2.0 seems not to be used
|
||||
* anywhere. <br>
|
||||
* ASF headers contains other chunks. As of this other readers of current
|
||||
* <b>package </b> are called from within.
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class AsfHeaderReader extends ChunkContainerReader<AsfHeader> {
|
||||
/**
|
||||
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||
*/
|
||||
private final static GUID[] APPLYING = {GUID.GUID_HEADER};
|
||||
|
||||
/**
|
||||
* ASF reader configured to extract all information.
|
||||
*/
|
||||
private final static AsfHeaderReader FULL_READER;
|
||||
/**
|
||||
* ASF reader configured to just extract information about audio streams.<br>
|
||||
* If the ASF file only contains one audio stream it works fine.<br>
|
||||
*/
|
||||
private final static AsfHeaderReader INFO_READER;
|
||||
|
||||
/**
|
||||
* ASF reader configured to just extract metadata information.<br>
|
||||
*/
|
||||
private final static AsfHeaderReader TAG_READER;
|
||||
|
||||
static {
|
||||
final List<Class<? extends ChunkReader>> readers = new ArrayList<Class<? extends ChunkReader>>();
|
||||
readers.add(FileHeaderReader.class);
|
||||
readers.add(StreamChunkReader.class);
|
||||
INFO_READER = new AsfHeaderReader(readers, true);
|
||||
readers.clear();
|
||||
readers.add(ContentDescriptionReader.class);
|
||||
readers.add(ContentBrandingReader.class);
|
||||
readers.add(LanguageListReader.class);
|
||||
readers.add(MetadataReader.class);
|
||||
/*
|
||||
* Create the header extension object readers with just content
|
||||
* description reader, extended content description reader, language
|
||||
* list reader and both metadata object readers.
|
||||
*/
|
||||
final AsfExtHeaderReader extReader = new AsfExtHeaderReader(readers,
|
||||
true);
|
||||
final AsfExtHeaderReader extReader2 = new AsfExtHeaderReader(readers,
|
||||
true);
|
||||
TAG_READER = new AsfHeaderReader(readers, true);
|
||||
TAG_READER.setExtendedHeaderReader(extReader);
|
||||
readers.add(FileHeaderReader.class);
|
||||
readers.add(StreamChunkReader.class);
|
||||
readers.add(EncodingChunkReader.class);
|
||||
readers.add(EncryptionChunkReader.class);
|
||||
readers.add(StreamBitratePropertiesReader.class);
|
||||
FULL_READER = new AsfHeaderReader(readers, false);
|
||||
FULL_READER.setExtendedHeaderReader(extReader2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of this reader.
|
||||
*
|
||||
* @param toRegister The chunk readers to utilize.
|
||||
* @param readChunkOnce if <code>true</code>, each chunk type (identified by chunk
|
||||
* GUID) will handled only once, if a reader is available, other
|
||||
* chunks will be discarded.
|
||||
*/
|
||||
public AsfHeaderReader(final List<Class<? extends ChunkReader>> toRegister,
|
||||
final boolean readChunkOnce) {
|
||||
super(toRegister, readChunkOnce);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Stream that will read from the specified
|
||||
* {@link RandomAccessFile};<br>
|
||||
*
|
||||
* @param raf data source to read from.
|
||||
* @return a stream which accesses the source.
|
||||
*/
|
||||
private static InputStream createStream(final RandomAccessFile raf) {
|
||||
return new FullRequestInputStream(new BufferedInputStream(
|
||||
new RandomAccessFileInputstream(raf)));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method extracts the full ASF-Header from the given file.<br>
|
||||
* If no header could be extracted <code>null</code> is returned. <br>
|
||||
*
|
||||
* @param file the ASF file to read.<br>
|
||||
* @return AsfHeader-Wrapper, or <code>null</code> if no supported ASF
|
||||
* header was found.
|
||||
* @throws IOException on I/O Errors.
|
||||
*/
|
||||
public static AsfHeader readHeader(final File file) throws IOException {
|
||||
final InputStream stream = new FileInputStream(file);
|
||||
final AsfHeader result = FULL_READER.read(Utils.readGUID(stream),
|
||||
stream, 0);
|
||||
stream.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method tries to extract a full ASF-header out of the given stream. <br>
|
||||
* If no header could be extracted <code>null</code> is returned. <br>
|
||||
*
|
||||
* @param file File which contains the ASF header.
|
||||
* @return AsfHeader-Wrapper, or <code>null</code> if no supported ASF
|
||||
* header was found.
|
||||
* @throws IOException Read errors
|
||||
*/
|
||||
public static AsfHeader readHeader(final RandomAccessFile file)
|
||||
throws IOException {
|
||||
final InputStream stream = createStream(file);
|
||||
return FULL_READER.read(Utils.readGUID(stream), stream, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method tries to extract an ASF-header out of the given stream, which
|
||||
* only contains information about the audio stream.<br>
|
||||
* If no header could be extracted <code>null</code> is returned. <br>
|
||||
*
|
||||
* @param file File which contains the ASF header.
|
||||
* @return AsfHeader-Wrapper, or <code>null</code> if no supported ASF
|
||||
* header was found.
|
||||
* @throws IOException Read errors
|
||||
*/
|
||||
public static AsfHeader readInfoHeader(final RandomAccessFile file)
|
||||
throws IOException {
|
||||
final InputStream stream = createStream(file);
|
||||
return INFO_READER.read(Utils.readGUID(stream), stream, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method tries to extract an ASF-header out of the given stream, which
|
||||
* only contains metadata.<br>
|
||||
* If no header could be extracted <code>null</code> is returned. <br>
|
||||
*
|
||||
* @param file File which contains the ASF header.
|
||||
* @return AsfHeader-Wrapper, or <code>null</code> if no supported ASF
|
||||
* header was found.
|
||||
* @throws IOException Read errors
|
||||
*/
|
||||
public static AsfHeader readTagHeader(final RandomAccessFile file)
|
||||
throws IOException {
|
||||
final InputStream stream = createStream(file);
|
||||
return TAG_READER.read(Utils.readGUID(stream), stream, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean canFail() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected AsfHeader createContainer(final long streamPosition,
|
||||
final BigInteger chunkLength, final InputStream stream)
|
||||
throws IOException {
|
||||
final long chunkCount = Utils.readUINT32(stream);
|
||||
/*
|
||||
* 2 reserved bytes. first should be equal to 0x01 and second 0x02. ASF
|
||||
* specification suggests to not read the content if second byte is not
|
||||
* 0x02.
|
||||
*/
|
||||
if (stream.read() != 1) {
|
||||
throw new IOException("No ASF"); //$NON-NLS-1$
|
||||
}
|
||||
if (stream.read() != 2) {
|
||||
throw new IOException("No ASF"); //$NON-NLS-1$
|
||||
}
|
||||
/*
|
||||
* Creating the resulting object
|
||||
*/
|
||||
return new AsfHeader(streamPosition, chunkLength, chunkCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public GUID[] getApplyingIds() {
|
||||
return APPLYING.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AsfExtHeaderReader}, which is to be used, when an header
|
||||
* extension object is found.
|
||||
*
|
||||
* @param extReader header extension object reader.
|
||||
*/
|
||||
public void setExtendedHeaderReader(final AsfExtHeaderReader extReader) {
|
||||
for (final GUID curr : extReader.getApplyingIds()) {
|
||||
this.readerMap.put(curr, extReader);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,176 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class creates a modified copy of an ASF file.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class AsfStreamer {
|
||||
|
||||
/**
|
||||
* Simply copies a chunk from <code>source</code> to
|
||||
* <code>destination</code>.<br>
|
||||
* The method assumes, that the GUID has already been read and will write
|
||||
* the provided one to the destination.<br>
|
||||
* The chunk length however will be read and used to determine the amount of
|
||||
* bytes to copy.
|
||||
*
|
||||
* @param guid GUID of the current chunk.
|
||||
* @param source source of an ASF chunk, which is to be located at the chunk
|
||||
* length field.
|
||||
* @param destination the destination to copy the chunk to.
|
||||
* @throws IOException on I/O errors.
|
||||
*/
|
||||
private void copyChunk(final GUID guid, final InputStream source,
|
||||
final OutputStream destination) throws IOException {
|
||||
final long chunkSize = Utils.readUINT64(source);
|
||||
destination.write(guid.getBytes());
|
||||
Utils.writeUINT64(chunkSize, destination);
|
||||
Utils.copy(source, destination, chunkSize - 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the <code>source</code> and applies the modifications provided by
|
||||
* the given <code>modifiers</code>, and puts it to <code>dest</code>.<br>
|
||||
* Each {@linkplain ChunkModifier modifier} is used only once, so if one
|
||||
* should be used multiple times, it should be added multiple times into the
|
||||
* list.<br>
|
||||
*
|
||||
* @param source the source ASF file
|
||||
* @param dest the destination to write the modified version to.
|
||||
* @param modifiers list of chunk modifiers to apply.
|
||||
* @throws IOException on I/O errors.
|
||||
*/
|
||||
public void createModifiedCopy(final InputStream source,
|
||||
final OutputStream dest, final List<ChunkModifier> modifiers)
|
||||
throws IOException {
|
||||
final List<ChunkModifier> modders = new ArrayList<ChunkModifier>();
|
||||
if (modifiers != null) {
|
||||
modders.addAll(modifiers);
|
||||
}
|
||||
// Read and check ASF GUID
|
||||
final GUID readGUID = Utils.readGUID(source);
|
||||
if (GUID.GUID_HEADER.equals(readGUID)) {
|
||||
// used to calculate differences
|
||||
long totalDiff = 0;
|
||||
long chunkDiff = 0;
|
||||
|
||||
// read header information
|
||||
final long headerSize = Utils.readUINT64(source);
|
||||
final long chunkCount = Utils.readUINT32(source);
|
||||
final byte[] reserved = new byte[2];
|
||||
reserved[0] = (byte) (source.read() & 0xFF);
|
||||
reserved[1] = (byte) (source.read() & 0xFF);
|
||||
|
||||
/*
|
||||
* bos will get all unmodified and modified header chunks. This is
|
||||
* necessary, because the header chunk (and file properties chunk)
|
||||
* need to be adjusted but are written in front of the others.
|
||||
*/
|
||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
// fileHeader will get the binary representation of the file
|
||||
// properties chunk, without GUID
|
||||
byte[] fileHeader = null;
|
||||
|
||||
// Iterate through all chunks
|
||||
for (long i = 0; i < chunkCount; i++) {
|
||||
// Read GUID
|
||||
final GUID curr = Utils.readGUID(source);
|
||||
// special case for file properties chunk
|
||||
if (GUID.GUID_FILE.equals(curr)) {
|
||||
final ByteArrayOutputStream tmp = new ByteArrayOutputStream();
|
||||
final long size = Utils.readUINT64(source);
|
||||
Utils.writeUINT64(size, tmp);
|
||||
Utils.copy(source, tmp, size - 24);
|
||||
fileHeader = tmp.toByteArray();
|
||||
} else {
|
||||
/*
|
||||
* Now look for ChunkModifier objects which modify the
|
||||
* current chunk
|
||||
*/
|
||||
boolean handled = false;
|
||||
for (int j = 0; j < modders.size() && !handled; j++) {
|
||||
if (modders.get(j).isApplicable(curr)) {
|
||||
// alter current chunk
|
||||
final ModificationResult result = modders.get(j)
|
||||
.modify(curr, source, bos);
|
||||
// remember size differences.
|
||||
chunkDiff += result.getChunkCountDifference();
|
||||
totalDiff += result.getByteDifference();
|
||||
// remove current modifier from index.
|
||||
modders.remove(j);
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
if (!handled) {
|
||||
// copy chunks which are not modified.
|
||||
copyChunk(curr, source, bos);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now apply the left modifiers.
|
||||
for (final ChunkModifier curr : modders) {
|
||||
// chunks, which were not in the source file, will be added to
|
||||
// the destination
|
||||
final ModificationResult result = curr.modify(null, null, bos);
|
||||
chunkDiff += result.getChunkCountDifference();
|
||||
totalDiff += result.getByteDifference();
|
||||
}
|
||||
/*
|
||||
* Now all header objects have been read or manipulated and stored
|
||||
* in the internal buffer (bos).
|
||||
*/
|
||||
// write ASF GUID
|
||||
dest.write(readGUID.getBytes());
|
||||
// write altered header object size
|
||||
Utils.writeUINT64(headerSize + totalDiff, dest);
|
||||
// write altered number of chunks
|
||||
Utils.writeUINT32(chunkCount + chunkDiff, dest);
|
||||
// write the reserved 2 bytes (0x01,0x02).
|
||||
dest.write(reserved);
|
||||
// write the new file header
|
||||
modifyFileHeader(new ByteArrayInputStream(fileHeader), dest,
|
||||
totalDiff);
|
||||
// write the header objects (chunks)
|
||||
dest.write(bos.toByteArray());
|
||||
// copy the rest of the file (data and index)
|
||||
Utils.flush(source, dest);
|
||||
} else {
|
||||
throw new IllegalArgumentException("No ASF header object.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a slight variation of
|
||||
* {@link #copyChunk(GUID, InputStream, OutputStream)}, it only handles file
|
||||
* property chunks correctly.<br>
|
||||
* The copied chunk will have the file size field modified by the given
|
||||
* <code>fileSizeDiff</code> value.
|
||||
*
|
||||
* @param source source of file properties chunk, located at its chunk length
|
||||
* field.
|
||||
* @param destination the destination to copy the chunk to.
|
||||
* @param fileSizeDiff the difference which should be applied. (negative values would
|
||||
* subtract the stored file size)
|
||||
* @throws IOException on I/O errors.
|
||||
*/
|
||||
private void modifyFileHeader(final InputStream source,
|
||||
final OutputStream destination, final long fileSizeDiff)
|
||||
throws IOException {
|
||||
destination.write(GUID.GUID_FILE.getBytes());
|
||||
final long chunkSize = Utils.readUINT64(source);
|
||||
Utils.writeUINT64(chunkSize, destination);
|
||||
destination.write(Utils.readGUID(source).getBytes());
|
||||
final long fileSize = Utils.readUINT64(source);
|
||||
Utils.writeUINT64(fileSize + fileSizeDiff, destination);
|
||||
Utils.copy(source, destination, chunkSize - 48);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||
import org.jaudiotagger.audio.asf.data.ChunkContainer;
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* This class represents a reader implementation, which is able to read ASF
|
||||
* objects (chunks) which store other objects (chunks) within them.<br>
|
||||
*
|
||||
* @param <ChunkType> The {@link ChunkContainer} instance, the implementation will
|
||||
* create.
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
abstract class ChunkContainerReader<ChunkType extends ChunkContainer>
|
||||
implements ChunkReader {
|
||||
|
||||
/**
|
||||
* Within this range, a {@link ChunkReader} should be aware if it fails.
|
||||
*/
|
||||
public final static int READ_LIMIT = 8192;
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
protected static final Logger LOGGER = Logger
|
||||
.getLogger("org.jaudiotabgger.audio"); //$NON-NLS-1$
|
||||
/**
|
||||
* If <code>true</code> each chunk type will only be read once.<br>
|
||||
*/
|
||||
protected final boolean eachChunkOnce;
|
||||
/**
|
||||
* Registers GUIDs to their reader classes.<br>
|
||||
*/
|
||||
protected final Map<GUID, ChunkReader> readerMap = new HashMap<GUID, ChunkReader>();
|
||||
/**
|
||||
* If <code>true</code> due to a {@linkplain #register(Class) registered}
|
||||
* chunk reader, all {@link InputStream} objects passed to
|
||||
* {@link #read(GUID, InputStream, long)} must support mark/reset.
|
||||
*/
|
||||
protected boolean hasFailingReaders = false;
|
||||
|
||||
/**
|
||||
* Creates a reader instance, which only utilizes the given list of chunk
|
||||
* readers.<br>
|
||||
*
|
||||
* @param toRegister List of {@link ChunkReader} class instances, which are to be
|
||||
* utilized by the instance.
|
||||
* @param readChunkOnce if <code>true</code>, each chunk type (identified by chunk
|
||||
* GUID) will handled only once, if a reader is available, other
|
||||
* chunks will be discarded.
|
||||
*/
|
||||
protected ChunkContainerReader(
|
||||
final List<Class<? extends ChunkReader>> toRegister,
|
||||
final boolean readChunkOnce) {
|
||||
this.eachChunkOnce = readChunkOnce;
|
||||
for (final Class<? extends ChunkReader> curr : toRegister) {
|
||||
register(curr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for the constraints of this class.
|
||||
*
|
||||
* @param stream stream to test.
|
||||
* @throws IllegalArgumentException If stream does not meet the requirements.
|
||||
*/
|
||||
protected void checkStream(final InputStream stream)
|
||||
throws IllegalArgumentException {
|
||||
if (this.hasFailingReaders && !stream.markSupported()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Stream has to support mark/reset.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by {@link #read(GUID, InputStream, long)} in order
|
||||
* to create the resulting object. Implementations of this class should now
|
||||
* return a new instance of their implementation specific result <b>AND</b>
|
||||
* all data should be read, until the list of chunks starts. (The
|
||||
* {@link ChunkContainer#getChunkEnd()} must return a sane result, too)<br>
|
||||
*
|
||||
* @param streamPosition position of the stream, the chunk starts.
|
||||
* @param chunkLength the length of the chunk (from chunk header)
|
||||
* @param stream to read the implementation specific information.
|
||||
* @return instance of the implementations result.
|
||||
* @throws IOException On I/O Errors and Invalid data.
|
||||
*/
|
||||
abstract protected ChunkType createContainer(long streamPosition,
|
||||
BigInteger chunkLength, InputStream stream) throws IOException;
|
||||
|
||||
/**
|
||||
* Gets a configured {@linkplain ChunkReader reader} instance for ASF
|
||||
* objects (chunks) with the specified <code>guid</code>.
|
||||
*
|
||||
* @param guid GUID which identifies the chunk to be read.
|
||||
* @return an appropriate reader implementation, <code>null</code> if not
|
||||
* {@linkplain #register(Class) registered}.
|
||||
*/
|
||||
protected ChunkReader getReader(final GUID guid) {
|
||||
return this.readerMap.get(guid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether {@link #getReader(GUID)} won't return <code>null</code>.<br>
|
||||
*
|
||||
* @param guid GUID which identifies the chunk to be read.
|
||||
* @return <code>true</code> if a reader is available.
|
||||
*/
|
||||
protected boolean isReaderAvailable(final GUID guid) {
|
||||
return this.readerMap.containsKey(guid);
|
||||
}
|
||||
|
||||
/**
|
||||
* This Method implements the reading of a chunk container.<br>
|
||||
*
|
||||
* @param guid GUID of the currently read container.
|
||||
* @param stream Stream which contains the chunk container.
|
||||
* @param chunkStart The start of the chunk container from stream start.<br>
|
||||
* For direct file streams one can assume <code>0</code> here.
|
||||
* @return <code>null</code> if no valid data found, else a Wrapper
|
||||
* containing all supported data.
|
||||
* @throws IOException Read errors.
|
||||
* @throws IllegalArgumentException If one used {@link ChunkReader} could
|
||||
* {@linkplain ChunkReader#canFail() fail} and the stream source
|
||||
* doesn't support mark/reset.
|
||||
*/
|
||||
public ChunkType read(final GUID guid, final InputStream stream,
|
||||
final long chunkStart) throws IOException, IllegalArgumentException {
|
||||
checkStream(stream);
|
||||
final CountingInputStream cis = new CountingInputStream(stream);
|
||||
if (!Arrays.asList(getApplyingIds()).contains(guid)) {
|
||||
throw new IllegalArgumentException(
|
||||
"provided GUID is not supported by this reader.");
|
||||
}
|
||||
// For Know the file pointer pointed to an ASF header chunk.
|
||||
final BigInteger chunkLen = Utils.readBig64(cis);
|
||||
/*
|
||||
* now read implementation specific information until the chunk
|
||||
* collection starts and create the resulting object.
|
||||
*/
|
||||
final ChunkType result = createContainer(chunkStart, chunkLen, cis);
|
||||
// 16 bytes have already been for providing the GUID
|
||||
long currentPosition = chunkStart + cis.getReadCount() + 16;
|
||||
|
||||
final HashSet<GUID> alreadyRead = new HashSet<GUID>();
|
||||
/*
|
||||
* Now reading header of chuncks.
|
||||
*/
|
||||
while (currentPosition < result.getChunkEnd()) {
|
||||
final GUID currentGUID = Utils.readGUID(cis);
|
||||
final boolean skip = this.eachChunkOnce
|
||||
&& (!isReaderAvailable(currentGUID) || !alreadyRead
|
||||
.add(currentGUID));
|
||||
Chunk chunk;
|
||||
/*
|
||||
* If one reader tells it could fail (new method), then check the
|
||||
* input stream for mark/reset. And use it if failed.
|
||||
*/
|
||||
if (!skip && isReaderAvailable(currentGUID)) {
|
||||
final ChunkReader reader = getReader(currentGUID);
|
||||
if (reader.canFail()) {
|
||||
cis.mark(READ_LIMIT);
|
||||
}
|
||||
chunk = getReader(currentGUID).read(currentGUID, cis,
|
||||
currentPosition);
|
||||
} else {
|
||||
chunk = ChunkHeaderReader.getInstance().read(currentGUID, cis,
|
||||
currentPosition);
|
||||
}
|
||||
if (chunk == null) {
|
||||
/*
|
||||
* Reader failed
|
||||
*/
|
||||
cis.reset();
|
||||
} else {
|
||||
if (!skip) {
|
||||
result.addChunk(chunk);
|
||||
}
|
||||
currentPosition = chunk.getChunkEnd();
|
||||
// Always take into account, that 16 bytes have been read prior
|
||||
// to calling this method
|
||||
assert cis.getReadCount() + chunkStart + 16 == currentPosition;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given reader.<br>
|
||||
*
|
||||
* @param <T> The actual reader implementation.
|
||||
* @param toRegister chunk reader which is to be registered.
|
||||
*/
|
||||
private <T extends ChunkReader> void register(final Class<T> toRegister) {
|
||||
try {
|
||||
final T reader = toRegister.newInstance();
|
||||
for (final GUID curr : reader.getApplyingIds()) {
|
||||
this.readerMap.put(curr, reader);
|
||||
}
|
||||
} catch (InstantiationException e) {
|
||||
LOGGER.severe(e.getMessage());
|
||||
} catch (IllegalAccessException e) {
|
||||
LOGGER.severe(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Default reader, Reads GUID and size out of an input stream and creates a
|
||||
* {@link org.jaudiotagger.audio.asf.data.Chunk}object, finally skips the
|
||||
* remaining chunk bytes.
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
final class ChunkHeaderReader implements ChunkReader {
|
||||
|
||||
/**
|
||||
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||
*/
|
||||
private final static GUID[] APPLYING = {GUID.GUID_UNSPECIFIED};
|
||||
|
||||
/**
|
||||
* Default instance.
|
||||
*/
|
||||
private static final ChunkHeaderReader INSTANCE = new ChunkHeaderReader();
|
||||
|
||||
/**
|
||||
* Hidden Utility class constructor.
|
||||
*/
|
||||
private ChunkHeaderReader() {
|
||||
// Hidden
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the reader.
|
||||
*
|
||||
* @return instance.
|
||||
*/
|
||||
public static ChunkHeaderReader getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean canFail() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public GUID[] getApplyingIds() {
|
||||
return APPLYING.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Chunk read(final GUID guid, final InputStream stream,
|
||||
final long chunkStart) throws IOException {
|
||||
final BigInteger chunkLen = Utils.readBig64(stream);
|
||||
stream.skip(chunkLen.longValue() - 24);
|
||||
return new Chunk(guid, chunkStart, chunkLen);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Reads an ASF chunk and writes a modified copy.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public interface ChunkModifier {
|
||||
|
||||
/**
|
||||
* Determines, whether the modifier handles chunks identified by given
|
||||
* <code>guid</code>.
|
||||
*
|
||||
* @param guid GUID to test.
|
||||
* @return <code>true</code>, if this modifier can be used to modify the
|
||||
* chunk.
|
||||
*/
|
||||
boolean isApplicable(GUID guid);
|
||||
|
||||
/**
|
||||
* Writes a modified copy of the chunk into the <code>destination.</code>.<br>
|
||||
*
|
||||
* @param guid GUID of the chunk to modify.
|
||||
* @param source a stream providing the chunk, starting at the chunks length
|
||||
* field.
|
||||
* @param destination destination for the modified chunk.
|
||||
* @return the differences between source and destination.
|
||||
* @throws IOException on I/O errors.
|
||||
*/
|
||||
ModificationResult modify(GUID guid, InputStream source,
|
||||
OutputStream destination) throws IOException;
|
||||
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* A ChunkReader provides methods for reading an ASF chunk.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public interface ChunkReader {
|
||||
|
||||
/**
|
||||
* Tells whether the reader can fail to return a valid chunk.<br>
|
||||
* The current Use would be a modified version of {@link StreamChunkReader},
|
||||
* which is configured to only manage audio streams. However, the primary
|
||||
* GUID for audio and video streams is the same. So if a stream shows itself
|
||||
* to be a video stream, the reader would return <code>null</code>.<br>
|
||||
*
|
||||
* @return <code>true</code>, if further analysis of the chunk can show,
|
||||
* that the reader is not applicable, despite the header GUID
|
||||
* {@linkplain #getApplyingIds() identification} told it can handle
|
||||
* the chunk.
|
||||
*/
|
||||
boolean canFail();
|
||||
|
||||
/**
|
||||
* Returns the GUIDs identifying the types of chunk, this reader will parse.<br>
|
||||
*
|
||||
* @return the GUIDs identifying the types of chunk, this reader will parse.<br>
|
||||
*/
|
||||
GUID[] getApplyingIds();
|
||||
|
||||
/**
|
||||
* Parses the chunk.
|
||||
*
|
||||
* @param guid the GUID of the chunks header, which is about to be read.
|
||||
* @param stream source to read chunk from.<br>
|
||||
* No {@link GUID} is expected at the currents stream position.
|
||||
* The length of the chunk is about to follow.
|
||||
* @param streamPosition the position in stream, the chunk starts.<br>
|
||||
* @return the read chunk. (Mostly a subclass of {@link Chunk}).<br>
|
||||
* @throws IOException On I/O Errors.
|
||||
*/
|
||||
Chunk read(GUID guid, InputStream stream, long streamPosition)
|
||||
throws IOException;
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This {@link ChunkModifier} implementation is meant to remove selected chunks.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
@SuppressWarnings({"ManualArrayToCollectionCopy"})
|
||||
public class ChunkRemover implements ChunkModifier {
|
||||
|
||||
/**
|
||||
* Stores the GUIDs, which are about to be removed by this modifier.<br>
|
||||
*/
|
||||
private final Set<GUID> toRemove;
|
||||
|
||||
/**
|
||||
* Creates an instance, for removing selected chunks.<br>
|
||||
*
|
||||
* @param guids the GUIDs which are about to be removed by this modifier.
|
||||
*/
|
||||
public ChunkRemover(final GUID... guids) {
|
||||
this.toRemove = new HashSet<GUID>();
|
||||
for (final GUID current : guids) {
|
||||
this.toRemove.add(current);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean isApplicable(final GUID guid) {
|
||||
return this.toRemove.contains(guid);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public ModificationResult modify(final GUID guid, final InputStream source,
|
||||
final OutputStream destination) throws IOException {
|
||||
ModificationResult result;
|
||||
if (guid == null) {
|
||||
// Now a chunk should be added, however, this implementation is for
|
||||
// removal.
|
||||
result = new ModificationResult(0, 0);
|
||||
} else {
|
||||
assert isApplicable(guid);
|
||||
// skip the chunk length minus 24 bytes for the already read length
|
||||
// and the guid.
|
||||
final long chunkLen = Utils.readUINT64(source);
|
||||
source.skip(chunkLen - 24);
|
||||
result = new ModificationResult(-1, -1 * chunkLen, guid);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||
import org.jaudiotagger.audio.asf.data.ContentBranding;
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* This reader is used to read the content branding object of ASF streams.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
* @see org.jaudiotagger.audio.asf.data.ContainerType#CONTENT_BRANDING
|
||||
* @see ContentBranding
|
||||
*/
|
||||
public class ContentBrandingReader implements ChunkReader {
|
||||
/**
|
||||
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||
*/
|
||||
private final static GUID[] APPLYING = {GUID.GUID_CONTENT_BRANDING};
|
||||
|
||||
/**
|
||||
* Should not be used for now.
|
||||
*/
|
||||
protected ContentBrandingReader() {
|
||||
// NOTHING toDo
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean canFail() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public GUID[] getApplyingIds() {
|
||||
return APPLYING.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Chunk read(final GUID guid, final InputStream stream,
|
||||
final long streamPosition) throws IOException {
|
||||
assert GUID.GUID_CONTENT_BRANDING.equals(guid);
|
||||
final BigInteger chunkSize = Utils.readBig64(stream);
|
||||
final long imageType = Utils.readUINT32(stream);
|
||||
assert imageType >= 0 && imageType <= 3 : imageType;
|
||||
final long imageDataSize = Utils.readUINT32(stream);
|
||||
assert imageType > 0 || imageDataSize == 0 : imageDataSize;
|
||||
assert imageDataSize < Integer.MAX_VALUE;
|
||||
final byte[] imageData = Utils.readBinary(stream, imageDataSize);
|
||||
final long copyRightUrlLen = Utils.readUINT32(stream);
|
||||
final String copyRight = new String(Utils.readBinary(stream,
|
||||
copyRightUrlLen));
|
||||
final long imageUrlLen = Utils.readUINT32(stream);
|
||||
final String imageUrl = new String(Utils
|
||||
.readBinary(stream, imageUrlLen));
|
||||
final ContentBranding result = new ContentBranding(streamPosition,
|
||||
chunkSize);
|
||||
result.setImage(imageType, imageData);
|
||||
result.setCopyRightURL(copyRight);
|
||||
result.setBannerImageURL(imageUrl);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||
import org.jaudiotagger.audio.asf.data.ContentDescription;
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Reads and interprets the data of a ASF chunk containing title, author... <br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
* @see org.jaudiotagger.audio.asf.data.ContentDescription
|
||||
*/
|
||||
public class ContentDescriptionReader implements ChunkReader {
|
||||
|
||||
/**
|
||||
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||
*/
|
||||
private final static GUID[] APPLYING = {GUID.GUID_CONTENTDESCRIPTION};
|
||||
|
||||
/**
|
||||
* Should not be used for now.
|
||||
*/
|
||||
protected ContentDescriptionReader() {
|
||||
// NOTHING toDo
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean canFail() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public GUID[] getApplyingIds() {
|
||||
return APPLYING.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next 5 UINT16 values as an array.<br>
|
||||
*
|
||||
* @param stream stream to read from
|
||||
* @return 5 int values read from stream.
|
||||
* @throws IOException on I/O Errors.
|
||||
*/
|
||||
private int[] getStringSizes(final InputStream stream) throws IOException {
|
||||
final int[] result = new int[5];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = Utils.readUINT16(stream);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Chunk read(final GUID guid, final InputStream stream,
|
||||
final long chunkStart) throws IOException {
|
||||
final BigInteger chunkSize = Utils.readBig64(stream);
|
||||
/*
|
||||
* Now comes 16-Bit values representing the length of the Strings which
|
||||
* follows.
|
||||
*/
|
||||
final int[] stringSizes = getStringSizes(stream);
|
||||
|
||||
/*
|
||||
* Now we know the String length of each occuring String.
|
||||
*/
|
||||
final String[] strings = new String[stringSizes.length];
|
||||
for (int i = 0; i < strings.length; i++) {
|
||||
if (stringSizes[i] > 0) {
|
||||
strings[i] = Utils
|
||||
.readFixedSizeUTF16Str(stream, stringSizes[i]);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Now create the result
|
||||
*/
|
||||
final ContentDescription result = new ContentDescription(chunkStart,
|
||||
chunkSize);
|
||||
if (stringSizes[0] > 0) {
|
||||
result.setTitle(strings[0]);
|
||||
}
|
||||
if (stringSizes[1] > 0) {
|
||||
result.setAuthor(strings[1]);
|
||||
}
|
||||
if (stringSizes[2] > 0) {
|
||||
result.setCopyright(strings[2]);
|
||||
}
|
||||
if (stringSizes[3] > 0) {
|
||||
result.setComment(strings[3]);
|
||||
}
|
||||
if (stringSizes[4] > 0) {
|
||||
result.setRating(strings[4]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* This implementation of {@link FilterInputStream} counts each read byte.<br>
|
||||
* So at each time, with {@link #getReadCount()} one can determine how many
|
||||
* bytes have been read, by this classes read and skip methods (mark and reset
|
||||
* are also taken into account).<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
class CountingInputStream extends FilterInputStream {
|
||||
|
||||
/**
|
||||
* If {@link #mark(int)} has been called, the current value of
|
||||
* {@link #readCount} is stored, in order to reset it upon {@link #reset()}.
|
||||
*/
|
||||
private long markPos;
|
||||
|
||||
/**
|
||||
* The amount of read or skipped bytes.
|
||||
*/
|
||||
private long readCount;
|
||||
|
||||
/**
|
||||
* Creates an instance, which delegates the commands to the given stream.
|
||||
*
|
||||
* @param stream stream to actually work with.
|
||||
*/
|
||||
public CountingInputStream(final InputStream stream) {
|
||||
super(stream);
|
||||
this.markPos = 0;
|
||||
this.readCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the given amount of bytes.
|
||||
*
|
||||
* @param amountRead number of bytes to increase.
|
||||
*/
|
||||
private synchronized void bytesRead(final long amountRead) {
|
||||
if (amountRead >= 0) {
|
||||
this.readCount += amountRead;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the readCount
|
||||
*/
|
||||
public synchronized long getReadCount() {
|
||||
return this.readCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public synchronized void mark(final int readlimit) {
|
||||
super.mark(readlimit);
|
||||
this.markPos = this.readCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
final int result = super.read();
|
||||
bytesRead(1);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int read(final byte[] destination, final int off, final int len)
|
||||
throws IOException {
|
||||
final int result = super.read(destination, off, len);
|
||||
bytesRead(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
super.reset();
|
||||
synchronized (this) {
|
||||
this.readCount = this.markPos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public long skip(final long amount) throws IOException {
|
||||
final long skipped = super.skip(amount);
|
||||
bytesRead(skipped);
|
||||
return skipped;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* This output stream wraps around another {@link OutputStream} and delegates
|
||||
* the write calls.<br>
|
||||
* Additionally all written bytes are counted and available by
|
||||
* {@link #getCount()}.
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class CountingOutputstream extends OutputStream {
|
||||
|
||||
/**
|
||||
* The stream to forward the write calls.
|
||||
*/
|
||||
private final OutputStream wrapped;
|
||||
/**
|
||||
* Stores the amount of bytes written.
|
||||
*/
|
||||
private long count = 0;
|
||||
|
||||
/**
|
||||
* Creates an instance which will delegate the write calls to the given
|
||||
* output stream.
|
||||
*
|
||||
* @param outputStream stream to wrap.
|
||||
*/
|
||||
public CountingOutputstream(final OutputStream outputStream) {
|
||||
super();
|
||||
assert outputStream != null;
|
||||
this.wrapped = outputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.wrapped.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
this.wrapped.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the count
|
||||
*/
|
||||
public long getCount() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void write(final byte[] bytes) throws IOException {
|
||||
this.wrapped.write(bytes);
|
||||
this.count += bytes.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void write(final byte[] bytes, final int off, final int len)
|
||||
throws IOException {
|
||||
this.wrapped.write(bytes, off, len);
|
||||
this.count += len;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void write(final int toWrite) throws IOException {
|
||||
this.wrapped.write(toWrite);
|
||||
this.count++;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||
import org.jaudiotagger.audio.asf.data.EncodingChunk;
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* This class reads the chunk containing encoding data <br>
|
||||
* <b>Warning:<b><br>
|
||||
* Implementation is not completed. More analysis of this chunk is needed.
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
class EncodingChunkReader implements ChunkReader {
|
||||
/**
|
||||
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||
*/
|
||||
private final static GUID[] APPLYING = {GUID.GUID_ENCODING};
|
||||
|
||||
/**
|
||||
* Should not be used for now.
|
||||
*/
|
||||
protected EncodingChunkReader() {
|
||||
// NOTHING toDo
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean canFail() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public GUID[] getApplyingIds() {
|
||||
return APPLYING.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Chunk read(final GUID guid, final InputStream stream,
|
||||
final long chunkStart) throws IOException {
|
||||
final BigInteger chunkLen = Utils.readBig64(stream);
|
||||
final EncodingChunk result = new EncodingChunk(chunkLen);
|
||||
int readBytes = 24;
|
||||
// Can't be interpreted
|
||||
/*
|
||||
* What do I think of this data, well it seems to be another GUID. Then
|
||||
* followed by a UINT16 indicating a length of data following (by half).
|
||||
* My test files just had the length of one and a two bytes zero.
|
||||
*/
|
||||
stream.skip(20);
|
||||
readBytes += 20;
|
||||
|
||||
/*
|
||||
* Read the number of strings which will follow
|
||||
*/
|
||||
final int stringCount = Utils.readUINT16(stream);
|
||||
readBytes += 2;
|
||||
|
||||
/*
|
||||
* Now reading the specified amount of strings.
|
||||
*/
|
||||
for (int i = 0; i < stringCount; i++) {
|
||||
final String curr = Utils.readCharacterSizedString(stream);
|
||||
result.addString(curr);
|
||||
readBytes += 4 + 2 * curr.length();
|
||||
}
|
||||
stream.skip(chunkLen.longValue() - readBytes);
|
||||
result.setPosition(chunkStart);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||
import org.jaudiotagger.audio.asf.data.EncryptionChunk;
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* This class reads the chunk containing encoding data <br>
|
||||
* <b>Warning:<b><br>
|
||||
* Implementation is not completed. More analysis of this chunk is needed.
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
class EncryptionChunkReader implements ChunkReader {
|
||||
|
||||
/**
|
||||
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||
*/
|
||||
private final static GUID[] APPLYING = {GUID.GUID_CONTENT_ENCRYPTION};
|
||||
|
||||
/**
|
||||
* Should not be used for now.
|
||||
*/
|
||||
protected EncryptionChunkReader() {
|
||||
// NOTHING toDo
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean canFail() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public GUID[] getApplyingIds() {
|
||||
return APPLYING.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Chunk read(final GUID guid, final InputStream stream,
|
||||
final long chunkStart) throws IOException {
|
||||
EncryptionChunk result;
|
||||
final BigInteger chunkLen = Utils.readBig64(stream);
|
||||
result = new EncryptionChunk(chunkLen);
|
||||
|
||||
// Can't be interpreted
|
||||
/*
|
||||
* Object ID GUID 128 Object Size QWORD 64 Secret Data Length DWORD 32
|
||||
* Secret Data INTEGER varies Protection Type Length DWORD 32 Protection
|
||||
* Type char varies Key ID Length DWORD 32 Key ID char varies License
|
||||
* URL Length DWORD 32 License URL char varies * Read the
|
||||
*/
|
||||
byte[] secretData;
|
||||
byte[] protectionType;
|
||||
byte[] keyID;
|
||||
byte[] licenseURL;
|
||||
|
||||
// Secret Data length
|
||||
int fieldLength;
|
||||
fieldLength = (int) Utils.readUINT32(stream);
|
||||
// Secret Data
|
||||
secretData = new byte[fieldLength + 1];
|
||||
stream.read(secretData, 0, fieldLength);
|
||||
secretData[fieldLength] = 0;
|
||||
|
||||
// Protection type Length
|
||||
fieldLength = 0;
|
||||
fieldLength = (int) Utils.readUINT32(stream);
|
||||
// Protection Data Length
|
||||
protectionType = new byte[fieldLength + 1];
|
||||
stream.read(protectionType, 0, fieldLength);
|
||||
protectionType[fieldLength] = 0;
|
||||
|
||||
// Key ID length
|
||||
fieldLength = 0;
|
||||
fieldLength = (int) Utils.readUINT32(stream);
|
||||
// Key ID
|
||||
keyID = new byte[fieldLength + 1];
|
||||
stream.read(keyID, 0, fieldLength);
|
||||
keyID[fieldLength] = 0;
|
||||
|
||||
// License URL length
|
||||
fieldLength = 0;
|
||||
fieldLength = (int) Utils.readUINT32(stream);
|
||||
// License URL
|
||||
licenseURL = new byte[fieldLength + 1];
|
||||
stream.read(licenseURL, 0, fieldLength);
|
||||
licenseURL[fieldLength] = 0;
|
||||
|
||||
result.setSecretData(new String(secretData));
|
||||
result.setProtectionType(new String(protectionType));
|
||||
result.setKeyID(new String(keyID));
|
||||
result.setLicenseURL(new String(licenseURL));
|
||||
|
||||
result.setPosition(chunkStart);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||
import org.jaudiotagger.audio.asf.data.FileHeader;
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Reads and interprets the data of the file header. <br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class FileHeaderReader implements ChunkReader {
|
||||
|
||||
/**
|
||||
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||
*/
|
||||
private final static GUID[] APPLYING = {GUID.GUID_FILE};
|
||||
|
||||
/**
|
||||
* Should not be used for now.
|
||||
*/
|
||||
protected FileHeaderReader() {
|
||||
// NOTHING toDo
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean canFail() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public GUID[] getApplyingIds() {
|
||||
return APPLYING.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Chunk read(final GUID guid, final InputStream stream,
|
||||
final long chunkStart) throws IOException {
|
||||
final BigInteger chunkLen = Utils.readBig64(stream);
|
||||
// Skip client GUID.
|
||||
stream.skip(16);
|
||||
final BigInteger fileSize = Utils.readBig64(stream);
|
||||
// fileTime in 100 ns since midnight of 1st january 1601 GMT
|
||||
final BigInteger fileTime = Utils.readBig64(stream);
|
||||
|
||||
final BigInteger packageCount = Utils.readBig64(stream);
|
||||
|
||||
final BigInteger timeEndPos = Utils.readBig64(stream);
|
||||
final BigInteger duration = Utils.readBig64(stream);
|
||||
final BigInteger timeStartPos = Utils.readBig64(stream);
|
||||
|
||||
final long flags = Utils.readUINT32(stream);
|
||||
|
||||
final long minPkgSize = Utils.readUINT32(stream);
|
||||
final long maxPkgSize = Utils.readUINT32(stream);
|
||||
final long uncompressedFrameSize = Utils.readUINT32(stream);
|
||||
|
||||
final FileHeader result = new FileHeader(chunkLen, fileSize, fileTime,
|
||||
packageCount, duration, timeStartPos, timeEndPos, flags,
|
||||
minPkgSize, maxPkgSize, uncompressedFrameSize);
|
||||
result.setPosition(chunkStart);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* This implementation repeatedly reads from the wrapped input stream until the
|
||||
* requested amount of bytes are read.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class FullRequestInputStream extends FilterInputStream {
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param source stream to read from.
|
||||
*/
|
||||
public FullRequestInputStream(final InputStream source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int read(final byte[] buffer) throws IOException {
|
||||
return read(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int read(final byte[] buffer, final int off, final int len)
|
||||
throws IOException {
|
||||
int totalRead = 0;
|
||||
int read;
|
||||
while (totalRead < len) {
|
||||
read = super.read(buffer, off + totalRead, len - totalRead);
|
||||
if (read >= 0) {
|
||||
totalRead += read;
|
||||
}
|
||||
if (read == -1) {
|
||||
throw new IOException((len - totalRead)
|
||||
+ " more bytes expected.");
|
||||
}
|
||||
}
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public long skip(final long amount) throws IOException {
|
||||
long skipped = 0;
|
||||
int zeroSkipCnt = 0;
|
||||
long currSkipped;
|
||||
while (skipped < amount) {
|
||||
currSkipped = super.skip(amount - skipped);
|
||||
if (currSkipped == 0) {
|
||||
zeroSkipCnt++;
|
||||
if (zeroSkipCnt == 2) {
|
||||
// If the skip value exceeds streams size, this and the
|
||||
// number is extremely large, this can lead to a very long
|
||||
// running loop.
|
||||
break;
|
||||
}
|
||||
}
|
||||
skipped += currSkipped;
|
||||
}
|
||||
return skipped;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
import org.jaudiotagger.audio.asf.data.LanguageList;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Reads and interprets the "Language List Object" of ASF files.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class LanguageListReader implements ChunkReader {
|
||||
|
||||
/**
|
||||
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||
*/
|
||||
private final static GUID[] APPLYING = {GUID.GUID_LANGUAGE_LIST};
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean canFail() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public GUID[] getApplyingIds() {
|
||||
return APPLYING.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Chunk read(final GUID guid, final InputStream stream,
|
||||
final long streamPosition) throws IOException {
|
||||
assert GUID.GUID_LANGUAGE_LIST.equals(guid);
|
||||
final BigInteger chunkLen = Utils.readBig64(stream);
|
||||
|
||||
final int readUINT16 = Utils.readUINT16(stream);
|
||||
|
||||
final LanguageList result = new LanguageList(streamPosition, chunkLen);
|
||||
for (int i = 0; i < readUINT16; i++) {
|
||||
final int langIdLen = (stream.read() & 0xFF);
|
||||
final String langId = Utils
|
||||
.readFixedSizeUTF16Str(stream, langIdLen);
|
||||
// langIdLen = 2 bytes for each char and optionally one zero
|
||||
// termination character
|
||||
assert langId.length() == langIdLen / 2 - 1
|
||||
|| langId.length() == langIdLen / 2;
|
||||
result.addLanguage(langId);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,154 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.*;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Reads an interprets "Metadata Object", "Metadata Library
|
||||
* Object" and "Extended Content Description" of ASF files.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class MetadataReader implements ChunkReader {
|
||||
|
||||
/**
|
||||
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||
*/
|
||||
private final static GUID[] APPLYING = {
|
||||
ContainerType.EXTENDED_CONTENT.getContainerGUID(),
|
||||
ContainerType.METADATA_OBJECT.getContainerGUID(),
|
||||
ContainerType.METADATA_LIBRARY_OBJECT.getContainerGUID()};
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean canFail() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public GUID[] getApplyingIds() {
|
||||
return APPLYING.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Chunk read(final GUID guid, final InputStream stream,
|
||||
final long streamPosition) throws IOException {
|
||||
final BigInteger chunkLen = Utils.readBig64(stream);
|
||||
|
||||
final MetadataContainer result = new MetadataContainer(guid,
|
||||
streamPosition, chunkLen);
|
||||
// isExtDesc will be set to true, if a extended content description
|
||||
// chunk is read
|
||||
// otherwise it is a metadata object, there are only slight differences
|
||||
final boolean isExtDesc = result.getContainerType() == ContainerType.EXTENDED_CONTENT;
|
||||
final int recordCount = Utils.readUINT16(stream);
|
||||
for (int i = 0; i < recordCount; i++) {
|
||||
int languageIndex = 0;
|
||||
int streamNumber = 0;
|
||||
if (!isExtDesc) {
|
||||
/*
|
||||
* Metadata objects have a language index and a stream number
|
||||
*/
|
||||
languageIndex = Utils.readUINT16(stream);
|
||||
assert languageIndex >= 0
|
||||
&& languageIndex < MetadataDescriptor.MAX_LANG_INDEX;
|
||||
assert result.getContainerType() == ContainerType.METADATA_LIBRARY_OBJECT
|
||||
|| languageIndex == 0;
|
||||
streamNumber = Utils.readUINT16(stream);
|
||||
assert streamNumber >= 0
|
||||
&& streamNumber <= MetadataDescriptor.MAX_STREAM_NUMBER;
|
||||
}
|
||||
final int nameLen = Utils.readUINT16(stream);
|
||||
String recordName = null;
|
||||
if (isExtDesc) {
|
||||
recordName = Utils.readFixedSizeUTF16Str(stream, nameLen);
|
||||
}
|
||||
final int dataType = Utils.readUINT16(stream);
|
||||
assert dataType >= 0 && dataType <= 6;
|
||||
final long dataLen = isExtDesc ? Utils.readUINT16(stream) : Utils
|
||||
.readUINT32(stream);
|
||||
assert dataLen >= 0;
|
||||
assert result.getContainerType() == ContainerType.METADATA_LIBRARY_OBJECT
|
||||
|| dataLen <= MetadataDescriptor.DWORD_MAXVALUE;
|
||||
if (!isExtDesc) {
|
||||
recordName = Utils.readFixedSizeUTF16Str(stream, nameLen);
|
||||
}
|
||||
final MetadataDescriptor descriptor = new MetadataDescriptor(result
|
||||
.getContainerType(), recordName, dataType, streamNumber,
|
||||
languageIndex);
|
||||
switch (dataType) {
|
||||
case MetadataDescriptor.TYPE_STRING:
|
||||
descriptor.setStringValue(Utils.readFixedSizeUTF16Str(stream,
|
||||
(int) dataLen));
|
||||
break;
|
||||
case MetadataDescriptor.TYPE_BINARY:
|
||||
descriptor.setBinaryValue(Utils.readBinary(stream, dataLen));
|
||||
break;
|
||||
case MetadataDescriptor.TYPE_BOOLEAN:
|
||||
assert isExtDesc && dataLen == 4 || !isExtDesc && dataLen == 2;
|
||||
descriptor.setBooleanValue(readBoolean(stream, (int) dataLen));
|
||||
break;
|
||||
case MetadataDescriptor.TYPE_DWORD:
|
||||
assert dataLen == 4;
|
||||
descriptor.setDWordValue(Utils.readUINT32(stream));
|
||||
break;
|
||||
case MetadataDescriptor.TYPE_WORD:
|
||||
assert dataLen == 2;
|
||||
descriptor.setWordValue(Utils.readUINT16(stream));
|
||||
break;
|
||||
case MetadataDescriptor.TYPE_QWORD:
|
||||
assert dataLen == 8;
|
||||
descriptor.setQWordValue(Utils.readUINT64(stream));
|
||||
break;
|
||||
case MetadataDescriptor.TYPE_GUID:
|
||||
assert dataLen == GUID.GUID_LENGTH;
|
||||
descriptor.setGUIDValue(Utils.readGUID(stream));
|
||||
break;
|
||||
default:
|
||||
// Unknown, hopefully the convention for the size of the
|
||||
// value
|
||||
// is given, so we could read it binary
|
||||
descriptor.setStringValue("Invalid datatype: "
|
||||
+ new String(Utils.readBinary(stream, dataLen)));
|
||||
}
|
||||
result.addDescriptor(descriptor);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the given amount of bytes and checks the last byte, if its equal to
|
||||
* one or zero (true / false).<br>
|
||||
* All other bytes must be zero. (if assertions enabled).
|
||||
*
|
||||
* @param stream stream to read from.
|
||||
* @param bytes amount of bytes
|
||||
* @return <code>true</code> or <code>false</code>.
|
||||
* @throws IOException on I/O Errors
|
||||
*/
|
||||
private boolean readBoolean(final InputStream stream, final int bytes)
|
||||
throws IOException {
|
||||
final byte[] tmp = new byte[bytes];
|
||||
stream.read(tmp);
|
||||
boolean result = false;
|
||||
for (int i = 0; i < bytes; i++) {
|
||||
if (i == bytes - 1) {
|
||||
result = tmp[i] == 1;
|
||||
assert tmp[i] == 0 || tmp[i] == 1;
|
||||
} else {
|
||||
assert tmp[i] == 0;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Structure to tell the differences occurred by altering a chunk.
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
final class ModificationResult {
|
||||
|
||||
/**
|
||||
* Stores the difference of bytes.<br>
|
||||
*/
|
||||
private final long byteDifference;
|
||||
|
||||
/**
|
||||
* Stores the difference of the amount of chunks.<br>
|
||||
* "-1" if the chunk disappeared upon modification.<br>
|
||||
* "0" if the chunk was just modified.<br>
|
||||
* "1" if a chunk has been created.<br>
|
||||
*/
|
||||
private final int chunkDifference;
|
||||
|
||||
/**
|
||||
* Stores all GUIDs, which have been read.<br>
|
||||
*/
|
||||
private final Set<GUID> occuredGUIDs = new HashSet<GUID>();
|
||||
|
||||
/**
|
||||
* Creates an instance.<br>
|
||||
*
|
||||
* @param chunkCountDiff amount of chunks appeared, disappeared
|
||||
* @param bytesDiffer amount of bytes added or removed.
|
||||
* @param occurred all GUIDs which have been occurred, during processing
|
||||
*/
|
||||
public ModificationResult(final int chunkCountDiff, final long bytesDiffer,
|
||||
final GUID... occurred) {
|
||||
assert occurred != null && occurred.length > 0;
|
||||
this.chunkDifference = chunkCountDiff;
|
||||
this.byteDifference = bytesDiffer;
|
||||
this.occuredGUIDs.addAll(Arrays.asList(occurred));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.<br>
|
||||
*
|
||||
* @param chunkCountDiff amount of chunks appeared, disappeared
|
||||
* @param bytesDiffer amount of bytes added or removed.
|
||||
* @param occurred all GUIDs which have been occurred, during processing
|
||||
*/
|
||||
public ModificationResult(final int chunkCountDiff, final long bytesDiffer,
|
||||
final Set<GUID> occurred) {
|
||||
this.chunkDifference = chunkCountDiff;
|
||||
this.byteDifference = bytesDiffer;
|
||||
this.occuredGUIDs.addAll(occurred);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the difference of bytes.
|
||||
*
|
||||
* @return the byte difference
|
||||
*/
|
||||
public long getByteDifference() {
|
||||
return this.byteDifference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the difference of the amount of chunks.
|
||||
*
|
||||
* @return the chunk count difference
|
||||
*/
|
||||
public int getChunkCountDifference() {
|
||||
return this.chunkDifference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all GUIDs which have been occurred during processing.
|
||||
*
|
||||
* @return see description.s
|
||||
*/
|
||||
public Set<GUID> getOccuredGUIDs() {
|
||||
return new HashSet<GUID>(this.occuredGUIDs);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
/**
|
||||
* Wraps a {@link RandomAccessFile} into an {@link InputStream}.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public final class RandomAccessFileInputstream extends InputStream {
|
||||
|
||||
/**
|
||||
* The file access to read from.<br>
|
||||
*/
|
||||
private final RandomAccessFile source;
|
||||
|
||||
/**
|
||||
* Creates an instance that will provide {@link InputStream} functionality
|
||||
* on the given {@link RandomAccessFile} by delegating calls.<br>
|
||||
*
|
||||
* @param file The file to read.
|
||||
*/
|
||||
public RandomAccessFileInputstream(final RandomAccessFile file) {
|
||||
super();
|
||||
if (file == null) {
|
||||
throw new IllegalArgumentException("null");
|
||||
}
|
||||
this.source = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return this.source.read();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int read(final byte[] buffer, final int off, final int len)
|
||||
throws IOException {
|
||||
return this.source.read(buffer, off, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public long skip(final long amount) throws IOException {
|
||||
if (amount < 0) {
|
||||
throw new IllegalArgumentException("invalid negative value");
|
||||
}
|
||||
long left = amount;
|
||||
while (left > Integer.MAX_VALUE) {
|
||||
this.source.skipBytes(Integer.MAX_VALUE);
|
||||
left -= Integer.MAX_VALUE;
|
||||
}
|
||||
return this.source.skipBytes((int) left);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
/**
|
||||
* Wraps a {@link RandomAccessFile} into an {@link OutputStream}.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public final class RandomAccessFileOutputStream extends OutputStream {
|
||||
|
||||
/**
|
||||
* the file to write to.
|
||||
*/
|
||||
private final RandomAccessFile targetFile;
|
||||
|
||||
/**
|
||||
* Creates an instance.<br>
|
||||
*
|
||||
* @param target file to write to.
|
||||
*/
|
||||
public RandomAccessFileOutputStream(final RandomAccessFile target) {
|
||||
super();
|
||||
this.targetFile = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void write(final byte[] bytes, final int off, final int len)
|
||||
throws IOException {
|
||||
this.targetFile.write(bytes, off, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void write(final int toWrite) throws IOException {
|
||||
this.targetFile.write(toWrite);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
import org.jaudiotagger.audio.asf.data.StreamBitratePropertiesChunk;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* This class reads the chunk containing the stream bitrate properties.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class StreamBitratePropertiesReader implements ChunkReader {
|
||||
|
||||
/**
|
||||
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||
*/
|
||||
private final static GUID[] APPLYING = {GUID.GUID_STREAM_BITRATE_PROPERTIES};
|
||||
|
||||
/**
|
||||
* Should not be used for now.
|
||||
*/
|
||||
protected StreamBitratePropertiesReader() {
|
||||
// NOTHING toDo
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean canFail() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public GUID[] getApplyingIds() {
|
||||
return APPLYING.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Chunk read(final GUID guid, final InputStream stream,
|
||||
final long chunkStart) throws IOException {
|
||||
final BigInteger chunkLen = Utils.readBig64(stream);
|
||||
final StreamBitratePropertiesChunk result = new StreamBitratePropertiesChunk(
|
||||
chunkLen);
|
||||
|
||||
/*
|
||||
* Read the amount of bitrate records
|
||||
*/
|
||||
final long recordCount = Utils.readUINT16(stream);
|
||||
for (int i = 0; i < recordCount; i++) {
|
||||
final int flags = Utils.readUINT16(stream);
|
||||
final long avgBitrate = Utils.readUINT32(stream);
|
||||
result.addBitrateRecord(flags & 0x00FF, avgBitrate);
|
||||
}
|
||||
|
||||
result.setPosition(chunkStart);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,188 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.*;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Reads and interprets the data of the audio or video stream information chunk. <br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class StreamChunkReader implements ChunkReader {
|
||||
|
||||
/**
|
||||
* The GUID this reader {@linkplain #getApplyingIds() applies to}
|
||||
*/
|
||||
private final static GUID[] APPLYING = {GUID.GUID_STREAM};
|
||||
|
||||
/**
|
||||
* Shouldn't be used for now.
|
||||
*/
|
||||
protected StreamChunkReader() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean canFail() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public GUID[] getApplyingIds() {
|
||||
return APPLYING.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Chunk read(final GUID guid, final InputStream stream,
|
||||
final long chunkStart) throws IOException {
|
||||
StreamChunk result = null;
|
||||
final BigInteger chunkLength = Utils.readBig64(stream);
|
||||
// Now comes GUID indicating whether stream content type is audio or
|
||||
// video
|
||||
final GUID streamTypeGUID = Utils.readGUID(stream);
|
||||
if (GUID.GUID_AUDIOSTREAM.equals(streamTypeGUID)
|
||||
|| GUID.GUID_VIDEOSTREAM.equals(streamTypeGUID)) {
|
||||
|
||||
// A GUID is indicating whether the stream is error
|
||||
// concealed
|
||||
final GUID errorConcealment = Utils.readGUID(stream);
|
||||
/*
|
||||
* Read the Time Offset
|
||||
*/
|
||||
final long timeOffset = Utils.readUINT64(stream);
|
||||
|
||||
final long typeSpecificDataSize = Utils.readUINT32(stream);
|
||||
final long streamSpecificDataSize = Utils.readUINT32(stream);
|
||||
|
||||
/*
|
||||
* Read a bit field. (Contains stream number, and whether the stream
|
||||
* content is encrypted.)
|
||||
*/
|
||||
final int mask = Utils.readUINT16(stream);
|
||||
final int streamNumber = mask & 127;
|
||||
final boolean contentEncrypted = (mask & 0x8000) != 0;
|
||||
|
||||
/*
|
||||
* Skip a reserved field
|
||||
*/
|
||||
stream.skip(4);
|
||||
|
||||
/*
|
||||
* very important to set for every stream type. The size of bytes
|
||||
* read by the specific stream type, in order to skip the remaining
|
||||
* unread bytes of the stream chunk.
|
||||
*/
|
||||
long streamSpecificBytes;
|
||||
|
||||
if (GUID.GUID_AUDIOSTREAM.equals(streamTypeGUID)) {
|
||||
/*
|
||||
* Reading audio specific information
|
||||
*/
|
||||
final AudioStreamChunk audioStreamChunk = new AudioStreamChunk(
|
||||
chunkLength);
|
||||
result = audioStreamChunk;
|
||||
|
||||
/*
|
||||
* read WAVEFORMATEX and format extension.
|
||||
*/
|
||||
final long compressionFormat = Utils.readUINT16(stream);
|
||||
final long channelCount = Utils.readUINT16(stream);
|
||||
final long samplingRate = Utils.readUINT32(stream);
|
||||
final long avgBytesPerSec = Utils.readUINT32(stream);
|
||||
final long blockAlignment = Utils.readUINT16(stream);
|
||||
final int bitsPerSample = Utils.readUINT16(stream);
|
||||
final int codecSpecificDataSize = Utils.readUINT16(stream);
|
||||
final byte[] codecSpecificData = new byte[codecSpecificDataSize];
|
||||
stream.read(codecSpecificData);
|
||||
|
||||
audioStreamChunk.setCompressionFormat(compressionFormat);
|
||||
audioStreamChunk.setChannelCount(channelCount);
|
||||
audioStreamChunk.setSamplingRate(samplingRate);
|
||||
audioStreamChunk.setAverageBytesPerSec(avgBytesPerSec);
|
||||
audioStreamChunk.setErrorConcealment(errorConcealment);
|
||||
audioStreamChunk.setBlockAlignment(blockAlignment);
|
||||
audioStreamChunk.setBitsPerSample(bitsPerSample);
|
||||
audioStreamChunk.setCodecData(codecSpecificData);
|
||||
|
||||
streamSpecificBytes = 18 + codecSpecificData.length;
|
||||
} else {
|
||||
/*
|
||||
* Reading video specific information
|
||||
*/
|
||||
final VideoStreamChunk videoStreamChunk = new VideoStreamChunk(
|
||||
chunkLength);
|
||||
result = videoStreamChunk;
|
||||
|
||||
final long pictureWidth = Utils.readUINT32(stream);
|
||||
final long pictureHeight = Utils.readUINT32(stream);
|
||||
|
||||
// Skip unknown field
|
||||
stream.skip(1);
|
||||
|
||||
/*
|
||||
* Now read the format specific data
|
||||
*/
|
||||
// Size of the data section (formatDataSize)
|
||||
stream.skip(2);
|
||||
|
||||
stream.skip(16);
|
||||
final byte[] fourCC = new byte[4];
|
||||
stream.read(fourCC);
|
||||
|
||||
videoStreamChunk.setPictureWidth(pictureWidth);
|
||||
videoStreamChunk.setPictureHeight(pictureHeight);
|
||||
videoStreamChunk.setCodecId(fourCC);
|
||||
|
||||
streamSpecificBytes = 31;
|
||||
}
|
||||
|
||||
/*
|
||||
* Setting common values for audio and video
|
||||
*/
|
||||
result.setStreamNumber(streamNumber);
|
||||
result.setStreamSpecificDataSize(streamSpecificDataSize);
|
||||
result.setTypeSpecificDataSize(typeSpecificDataSize);
|
||||
result.setTimeOffset(timeOffset);
|
||||
result.setContentEncrypted(contentEncrypted);
|
||||
result.setPosition(chunkStart);
|
||||
/*
|
||||
* Now skip remainder of chunks bytes. chunk-length - 24 (size of
|
||||
* GUID and chunklen) - streamSpecificBytes(stream type specific
|
||||
* data) - 54 (common data)
|
||||
*/
|
||||
stream
|
||||
.skip(chunkLength.longValue() - 24 - streamSpecificBytes
|
||||
- 54);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Implementors can write themselves directly to an output stream, and have the
|
||||
* ability to tell the size they would need, as well as determine if they are
|
||||
* empty.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public interface WriteableChunk {
|
||||
|
||||
/**
|
||||
* This method calculates the total amount of bytes, the chunk would consume
|
||||
* in an ASF file.<br>
|
||||
*
|
||||
* @return amount of bytes the chunk would currently need in an ASF file.
|
||||
*/
|
||||
long getCurrentAsfChunkSize();
|
||||
|
||||
/**
|
||||
* Returns the GUID of the chunk.
|
||||
*
|
||||
* @return GUID of the chunk.
|
||||
*/
|
||||
GUID getGuid();
|
||||
|
||||
/**
|
||||
* <code>true</code> if it is not necessary to write the chunk into an ASF
|
||||
* file, since it contains no information.
|
||||
*
|
||||
* @return <code>true</code> if no useful data will be preserved.
|
||||
*/
|
||||
boolean isEmpty();
|
||||
|
||||
/**
|
||||
* Writes the chunk into the specified output stream, as ASF stream chunk.<br>
|
||||
*
|
||||
* @param out stream to write into.
|
||||
* @return amount of bytes written.
|
||||
* @throws IOException on I/O errors
|
||||
*/
|
||||
long writeInto(OutputStream out) throws IOException;
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
package org.jaudiotagger.audio.asf.io;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
import org.jaudiotagger.audio.asf.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* A chunk modifier which works with information provided by
|
||||
* {@link WriteableChunk} objects.<br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class WriteableChunkModifer implements ChunkModifier {
|
||||
|
||||
/**
|
||||
* The chunk to write.
|
||||
*/
|
||||
private final WriteableChunk writableChunk;
|
||||
|
||||
/**
|
||||
* Creates an instance.<br>
|
||||
*
|
||||
* @param chunk chunk to write
|
||||
*/
|
||||
public WriteableChunkModifer(final WriteableChunk chunk) {
|
||||
this.writableChunk = chunk;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean isApplicable(final GUID guid) {
|
||||
return guid.equals(this.writableChunk.getGuid());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public ModificationResult modify(final GUID guid, final InputStream chunk,
|
||||
OutputStream destination) throws IOException { // NOPMD by Christian Laireiter on 5/9/09 5:03 PM
|
||||
int chunkDiff = 0;
|
||||
long newSize = 0;
|
||||
long oldSize = 0;
|
||||
/*
|
||||
* Replace the outputstream with the counting one, only if assert's are
|
||||
* evaluated.
|
||||
*/
|
||||
assert (destination = new CountingOutputstream(destination)) != null;
|
||||
if (!this.writableChunk.isEmpty()) {
|
||||
newSize = this.writableChunk.writeInto(destination);
|
||||
assert newSize == this.writableChunk.getCurrentAsfChunkSize();
|
||||
/*
|
||||
* If assert's are evaluated, we have replaced destination by a
|
||||
* CountingOutpustream and can now verify if
|
||||
* getCurrentAsfChunkSize() really works correctly.
|
||||
*/
|
||||
assert ((CountingOutputstream) destination).getCount() == newSize;
|
||||
if (guid == null) {
|
||||
chunkDiff++;
|
||||
}
|
||||
|
||||
}
|
||||
if (guid != null) {
|
||||
assert isApplicable(guid);
|
||||
if (this.writableChunk.isEmpty()) {
|
||||
chunkDiff--;
|
||||
}
|
||||
oldSize = Utils.readUINT64(chunk);
|
||||
chunk.skip(oldSize - 24);
|
||||
}
|
||||
return new ModificationResult(chunkDiff, (newSize - oldSize), guid);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
|
||||
|
||||
<html long="en">
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Classes for reading and writing Microsoft Advanced Systems Format files.
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
<!-- package.html by Gary McGath -->
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
|
||||
|
||||
<html long="en">
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Classes for Microsoft Advanced Systems Format files.
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
<!-- package.html by Gary McGath -->
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.util;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.Chunk;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* This class is needed for ordering all types of
|
||||
* {@link org.jaudiotagger.audio.asf.data.Chunk}s ascending by their Position. <br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public final class ChunkPositionComparator implements Comparator<Chunk>,
|
||||
Serializable {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -6337108235272376289L;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public int compare(final Chunk first, final Chunk second) {
|
||||
return Long.valueOf(first.getPosition())
|
||||
.compareTo(second.getPosition());
|
||||
}
|
||||
}
|
|
@ -1,195 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.util;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.*;
|
||||
import org.jaudiotagger.tag.FieldKey;
|
||||
import org.jaudiotagger.tag.Tag;
|
||||
import org.jaudiotagger.tag.asf.*;
|
||||
import org.jaudiotagger.tag.reference.GenreTypes;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class provides functionality to convert
|
||||
* {@link org.jaudiotagger.audio.asf.data.AsfHeader}objects into
|
||||
* {@link org.jaudiotagger.tag.Tag}objects.<br>
|
||||
*
|
||||
* @author Christian Laireiter (liree)
|
||||
*/
|
||||
public final class TagConverter {
|
||||
|
||||
/**
|
||||
* Hidden utility class constructor.
|
||||
*/
|
||||
private TagConverter() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* This method assigns those tags of <code>tag</code> which are defined to
|
||||
* be common by jaudiotagger. <br>
|
||||
*
|
||||
* @param tag The tag from which the values are gathered. <br>
|
||||
* Assigned values are: <br>
|
||||
* @param description The extended content description which should receive the
|
||||
* values. <br>
|
||||
* <b>Warning: </b> the common values will be replaced.
|
||||
*/
|
||||
public static void assignCommonTagValues(Tag tag,
|
||||
MetadataContainer description) {
|
||||
assert description.getContainerType() == ContainerType.EXTENDED_CONTENT;
|
||||
MetadataDescriptor tmp;
|
||||
if (!Utils.isBlank(tag.getFirst(FieldKey.ALBUM))) {
|
||||
tmp = new MetadataDescriptor(description.getContainerType(),
|
||||
AsfFieldKey.ALBUM.getFieldName(),
|
||||
MetadataDescriptor.TYPE_STRING);
|
||||
tmp.setStringValue(tag.getFirst(FieldKey.ALBUM));
|
||||
description.removeDescriptorsByName(tmp.getName());
|
||||
description.addDescriptor(tmp);
|
||||
} else {
|
||||
description.removeDescriptorsByName(AsfFieldKey.ALBUM
|
||||
.getFieldName());
|
||||
}
|
||||
if (!Utils.isBlank(tag.getFirst(FieldKey.TRACK))) {
|
||||
tmp = new MetadataDescriptor(description.getContainerType(),
|
||||
AsfFieldKey.TRACK.getFieldName(),
|
||||
MetadataDescriptor.TYPE_STRING);
|
||||
tmp.setStringValue(tag.getFirst(FieldKey.TRACK));
|
||||
description.removeDescriptorsByName(tmp.getName());
|
||||
description.addDescriptor(tmp);
|
||||
} else {
|
||||
description.removeDescriptorsByName(AsfFieldKey.TRACK
|
||||
.getFieldName());
|
||||
}
|
||||
if (!Utils.isBlank(tag.getFirst(FieldKey.YEAR))) {
|
||||
tmp = new MetadataDescriptor(description.getContainerType(),
|
||||
AsfFieldKey.YEAR.getFieldName(),
|
||||
MetadataDescriptor.TYPE_STRING);
|
||||
tmp.setStringValue(tag.getFirst(FieldKey.YEAR));
|
||||
description.removeDescriptorsByName(tmp.getName());
|
||||
description.addDescriptor(tmp);
|
||||
} else {
|
||||
description
|
||||
.removeDescriptorsByName(AsfFieldKey.YEAR.getFieldName());
|
||||
}
|
||||
if (!Utils.isBlank(tag.getFirst(FieldKey.GENRE))) {
|
||||
// Write Genre String value
|
||||
tmp = new MetadataDescriptor(description.getContainerType(),
|
||||
AsfFieldKey.GENRE.getFieldName(),
|
||||
MetadataDescriptor.TYPE_STRING);
|
||||
tmp.setStringValue(tag.getFirst(FieldKey.GENRE));
|
||||
description.removeDescriptorsByName(tmp.getName());
|
||||
description.addDescriptor(tmp);
|
||||
Integer genreNum = GenreTypes.getInstanceOf().getIdForName(
|
||||
tag.getFirst(FieldKey.GENRE));
|
||||
// ..and if it is one of the standard genre types used the id as
|
||||
// well
|
||||
if (genreNum != null) {
|
||||
tmp = new MetadataDescriptor(description.getContainerType(),
|
||||
AsfFieldKey.GENRE_ID.getFieldName(),
|
||||
MetadataDescriptor.TYPE_STRING);
|
||||
tmp.setStringValue("(" + genreNum + ")");
|
||||
description.removeDescriptorsByName(tmp.getName());
|
||||
description.addDescriptor(tmp);
|
||||
} else {
|
||||
description.removeDescriptorsByName(AsfFieldKey.GENRE_ID
|
||||
.getFieldName());
|
||||
}
|
||||
} else {
|
||||
description.removeDescriptorsByName(AsfFieldKey.GENRE
|
||||
.getFieldName());
|
||||
description.removeDescriptorsByName(AsfFieldKey.GENRE_ID
|
||||
.getFieldName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates a {@link Tag}and fills it with the contents of the
|
||||
* given {@link AsfHeader}.<br>
|
||||
*
|
||||
* @param source The ASF header which contains the information. <br>
|
||||
* @return A Tag with all its values.
|
||||
*/
|
||||
public static AsfTag createTagOf(AsfHeader source) {
|
||||
// TODO do we need to copy here.
|
||||
AsfTag result = new AsfTag(true);
|
||||
for (int i = 0; i < ContainerType.values().length; i++) {
|
||||
MetadataContainer current = source
|
||||
.findMetadataContainer(ContainerType.values()[i]);
|
||||
if (current != null) {
|
||||
List<MetadataDescriptor> descriptors = current.getDescriptors();
|
||||
for (MetadataDescriptor descriptor : descriptors) {
|
||||
AsfTagField toAdd;
|
||||
if (descriptor.getType() == MetadataDescriptor.TYPE_BINARY) {
|
||||
if (descriptor.getName().equals(
|
||||
AsfFieldKey.COVER_ART.getFieldName())) {
|
||||
toAdd = new AsfTagCoverField(descriptor);
|
||||
} else if (descriptor.getName().equals(
|
||||
AsfFieldKey.BANNER_IMAGE.getFieldName())) {
|
||||
toAdd = new AsfTagBannerField(descriptor);
|
||||
} else {
|
||||
toAdd = new AsfTagField(descriptor);
|
||||
}
|
||||
} else {
|
||||
toAdd = new AsfTagTextField(descriptor);
|
||||
}
|
||||
result.addField(toAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method distributes the tags fields among the
|
||||
* {@linkplain ContainerType#getOrdered()} {@linkplain MetadataContainer
|
||||
* containers}.
|
||||
*
|
||||
* @param tag the tag with the fields to distribute.
|
||||
* @return distribution
|
||||
*/
|
||||
public static MetadataContainer[] distributeMetadata(final AsfTag tag) {
|
||||
final Iterator<AsfTagField> asfFields = tag.getAsfFields();
|
||||
final MetadataContainer[] createContainers = MetadataContainerFactory
|
||||
.getInstance().createContainers(ContainerType.getOrdered());
|
||||
boolean assigned;
|
||||
AsfTagField current;
|
||||
while (asfFields.hasNext()) {
|
||||
current = asfFields.next();
|
||||
assigned = false;
|
||||
for (int i = 0; !assigned && i < createContainers.length; i++) {
|
||||
if (ContainerType.areInCorrectOrder(createContainers[i]
|
||||
.getContainerType(), AsfFieldKey.getAsfFieldKey(
|
||||
current.getId()).getHighestContainer())) {
|
||||
if (createContainers[i].isAddSupported(current
|
||||
.getDescriptor())) {
|
||||
createContainers[i].addDescriptor(current
|
||||
.getDescriptor());
|
||||
assigned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert assigned;
|
||||
}
|
||||
return createContainers;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,497 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.asf.util;
|
||||
|
||||
import org.jaudiotagger.audio.asf.data.AsfHeader;
|
||||
import org.jaudiotagger.audio.asf.data.GUID;
|
||||
import org.jaudiotagger.logging.ErrorMessage;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
/**
|
||||
* Some static Methods which are used in several Classes. <br>
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class Utils {
|
||||
|
||||
public static final long DIFF_BETWEEN_ASF_DATE_AND_JAVA_DATE = 11644470000000l;
|
||||
/**
|
||||
* Stores the default line separator of the current underlying system.
|
||||
*/
|
||||
public final static String LINE_SEPARATOR = System
|
||||
.getProperty("line.separator"); //$NON-NLS-1$
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final int MAXIMUM_STRING_LENGTH_ALLOWED = 32766;
|
||||
|
||||
/**
|
||||
* This method checks given string will not exceed limit in bytes[] when
|
||||
* converted UTF-16LE encoding (2 bytes per character) and checks whether
|
||||
* the length doesn't exceed 65535 bytes. <br>
|
||||
*
|
||||
* @param value The string to check.
|
||||
* @throws IllegalArgumentException If byte representation takes more than 65535 bytes.
|
||||
*/
|
||||
public static void checkStringLengthNullSafe(String value)
|
||||
throws IllegalArgumentException {
|
||||
if (value != null) {
|
||||
if (value.length() > MAXIMUM_STRING_LENGTH_ALLOWED) {
|
||||
throw new IllegalArgumentException(
|
||||
ErrorMessage.WMA_LENGTH_OF_STRING_IS_TOO_LARGE
|
||||
.getMsg((value.length() * 2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value String to check for null
|
||||
* @return true unless string is too long
|
||||
*/
|
||||
public static boolean isStringLengthValidNullSafe(String value) {
|
||||
if (value != null) {
|
||||
if (value.length() > MAXIMUM_STRING_LENGTH_ALLOWED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* effectively copies a specified amount of bytes from one stream to
|
||||
* another.
|
||||
*
|
||||
* @param source stream to read from
|
||||
* @param dest stream to write to
|
||||
* @param amount amount of bytes to copy
|
||||
* @throws IOException on I/O errors, and if the source stream depletes before all
|
||||
* bytes have been copied.
|
||||
*/
|
||||
public static void copy(InputStream source, OutputStream dest, long amount)
|
||||
throws IOException {
|
||||
byte[] buf = new byte[8192];
|
||||
long copied = 0;
|
||||
while (copied < amount) {
|
||||
int toRead = 8192;
|
||||
if ((amount - copied) < 8192) {
|
||||
toRead = (int) (amount - copied);
|
||||
}
|
||||
int read = source.read(buf, 0, toRead);
|
||||
if (read == -1) {
|
||||
throw new IOException(
|
||||
"Inputstream has to continue for another "
|
||||
+ (amount - copied) + " bytes.");
|
||||
}
|
||||
dest.write(buf, 0, read);
|
||||
copied += read;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all of the source to the destination.<br>
|
||||
*
|
||||
* @param source source to read from
|
||||
* @param dest stream to write to
|
||||
* @throws IOException on I/O errors.
|
||||
*/
|
||||
public static void flush(final InputStream source, final OutputStream dest)
|
||||
throws IOException {
|
||||
final byte[] buf = new byte[8192];
|
||||
int read;
|
||||
while ((read = source.read(buf)) != -1) {
|
||||
dest.write(buf, 0, read);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will create a byte[] at the size of <code>byteCount</code>
|
||||
* and insert the bytes of <code>value</code> (starting from lowset byte)
|
||||
* into it. <br>
|
||||
* You can easily create a Word (16-bit), DWORD (32-bit), QWORD (64 bit) out
|
||||
* of the value, ignoring the original type of value, since java
|
||||
* automatically performs transformations. <br>
|
||||
* <b>Warning: </b> This method works with unsigned numbers only.
|
||||
*
|
||||
* @param value The value to be written into the result.
|
||||
* @param byteCount The number of bytes the array has got.
|
||||
* @return A byte[] with the size of <code>byteCount</code> containing the
|
||||
* lower byte values of <code>value</code>.
|
||||
*/
|
||||
public static byte[] getBytes(final long value, final int byteCount) {
|
||||
byte[] result = new byte[byteCount];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = (byte) ((value >>> (i * 8)) & 0xFF);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to convert the given string into a byte sequence which
|
||||
* has the format of the charset given.
|
||||
*
|
||||
* @param source string to convert.
|
||||
* @param charset charset to apply
|
||||
* @return the source's binary representation according to the charset.
|
||||
*/
|
||||
public static byte[] getBytes(final String source, final Charset charset) {
|
||||
assert charset != null;
|
||||
assert source != null;
|
||||
final ByteBuffer encoded = charset.encode(source);
|
||||
final byte[] result = new byte[encoded.limit()];
|
||||
encoded.rewind();
|
||||
encoded.get(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Since date values in ASF files are given in 100 ns steps since first
|
||||
* january of 1601 a little conversion must be done. <br>
|
||||
* This method converts a date given in described manner to a calendar.
|
||||
*
|
||||
* @param fileTime
|
||||
* Time in 100ns since 1 jan 1601
|
||||
* @return Calendar holding the date representation.
|
||||
*/
|
||||
/* Old method that ran very slowely and doesnt logical correct, how does dividing something
|
||||
at 10-4 by 10,000 convert it to 10 -3
|
||||
public static GregorianCalendar getDateOf(final BigInteger fileTime) {
|
||||
final GregorianCalendar result = new GregorianCalendar(1601, 0, 1);
|
||||
// lose anything beyond milliseconds, because calendar can't handle
|
||||
// less value
|
||||
BigInteger time = fileTime.divide(new BigInteger("10000")); //$NON-NLS-1$
|
||||
final BigInteger maxInt = new BigInteger(String
|
||||
.valueOf(Integer.MAX_VALUE));
|
||||
while (time.compareTo(maxInt) > 0) {
|
||||
result.add(Calendar.MILLISECOND, Integer.MAX_VALUE);
|
||||
time = time.subtract(maxInt);
|
||||
}
|
||||
result.add(Calendar.MILLISECOND, time.intValue());
|
||||
return result;
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Date values in ASF files are given in 100 ns (10 exp -4) steps since first
|
||||
*
|
||||
* @param fileTime Time in 100ns since 1 jan 1601
|
||||
* @return Calendar holding the date representation.
|
||||
*/
|
||||
public static GregorianCalendar getDateOf(final BigInteger fileTime) {
|
||||
final GregorianCalendar result = new GregorianCalendar();
|
||||
|
||||
// Divide by 10 to convert from -4 to -3 (millisecs)
|
||||
BigInteger time = fileTime.divide(new BigInteger("10"));
|
||||
// Construct Date taking into the diff between 1601 and 1970
|
||||
Date date = new Date(time.longValue() - DIFF_BETWEEN_ASF_DATE_AND_JAVA_DATE);
|
||||
result.setTime(date);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the given string is <code>null</code> or just contains
|
||||
* whitespace characters.
|
||||
*
|
||||
* @param toTest String to test.
|
||||
* @return see description.
|
||||
*/
|
||||
public static boolean isBlank(String toTest) {
|
||||
if (toTest == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < toTest.length(); i++) {
|
||||
if (!Character.isWhitespace(toTest.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads 8 bytes from stream and interprets them as a UINT64 which is
|
||||
* returned as {@link BigInteger}.<br>
|
||||
*
|
||||
* @param stream stream to readm from.
|
||||
* @return a BigInteger which represents the read 8 bytes value.
|
||||
* @throws IOException if problem reading bytes
|
||||
*/
|
||||
public static BigInteger readBig64(InputStream stream) throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
byte[] oa = new byte[8];
|
||||
int read = stream.read(bytes);
|
||||
if (read != 8) {
|
||||
// 8 bytes mandatory.
|
||||
throw new EOFException();
|
||||
}
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
oa[7 - i] = bytes[i];
|
||||
}
|
||||
return new BigInteger(oa);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads <code>size</code> bytes from the stream.<br>
|
||||
*
|
||||
* @param stream stream to read from.
|
||||
* @param size amount of bytes to read.
|
||||
* @return the read bytes.
|
||||
* @throws IOException on I/O errors.
|
||||
*/
|
||||
public static byte[] readBinary(InputStream stream, long size)
|
||||
throws IOException {
|
||||
byte[] result = new byte[(int) size];
|
||||
stream.read(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method reads a UTF-16 String, which length is given on the number of
|
||||
* characters it consists of. <br>
|
||||
* The stream must be at the number of characters. This number contains the
|
||||
* terminating zero character (UINT16).
|
||||
*
|
||||
* @param stream Input source
|
||||
* @return String
|
||||
* @throws IOException read errors
|
||||
*/
|
||||
public static String readCharacterSizedString(InputStream stream)
|
||||
throws IOException {
|
||||
StringBuilder result = new StringBuilder();
|
||||
int strLen = readUINT16(stream);
|
||||
int character = stream.read();
|
||||
character |= stream.read() << 8;
|
||||
do {
|
||||
if (character != 0) {
|
||||
result.append((char) character);
|
||||
character = stream.read();
|
||||
character |= stream.read() << 8;
|
||||
}
|
||||
} while (character != 0 || (result.length() + 1) > strLen);
|
||||
if (strLen != (result.length() + 1)) {
|
||||
throw new IllegalStateException(
|
||||
"Invalid Data for current interpretation"); //$NON-NLS-1$
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method reads a UTF-16 encoded String. <br>
|
||||
* For the use this method the number of bytes used by current string must
|
||||
* be known. <br>
|
||||
* The ASF specification recommends that those strings end with a
|
||||
* terminating zero. However it also says that it is not always the case.
|
||||
*
|
||||
* @param stream Input source
|
||||
* @param strLen Number of bytes the String may take.
|
||||
* @return read String.
|
||||
* @throws IOException read errors.
|
||||
*/
|
||||
public static String readFixedSizeUTF16Str(InputStream stream, int strLen)
|
||||
throws IOException {
|
||||
byte[] strBytes = new byte[strLen];
|
||||
int read = stream.read(strBytes);
|
||||
if (read == strBytes.length) {
|
||||
if (strBytes.length >= 2) {
|
||||
/*
|
||||
* Zero termination is recommended but optional. So check and
|
||||
* if, remove.
|
||||
*/
|
||||
if (strBytes[strBytes.length - 1] == 0
|
||||
&& strBytes[strBytes.length - 2] == 0) {
|
||||
byte[] copy = new byte[strBytes.length - 2];
|
||||
System.arraycopy(strBytes, 0, copy, 0, strBytes.length - 2);
|
||||
strBytes = copy;
|
||||
}
|
||||
}
|
||||
return new String(strBytes, "UTF-16LE");
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Couldn't read the necessary amount of bytes.");
|
||||
}
|
||||
|
||||
/**
|
||||
* This Method reads a GUID (which is a 16 byte long sequence) from the
|
||||
* given <code>raf</code> and creates a wrapper. <br>
|
||||
* <b>Warning </b>: <br>
|
||||
* There is no way of telling if a byte sequence is a guid or not. The next
|
||||
* 16 bytes will be interpreted as a guid, whether it is or not.
|
||||
*
|
||||
* @param stream Input source.
|
||||
* @return A class wrapping the guid.
|
||||
* @throws IOException happens when the file ends before guid could be extracted.
|
||||
*/
|
||||
public static GUID readGUID(InputStream stream) throws IOException {
|
||||
if (stream == null) {
|
||||
throw new IllegalArgumentException("Argument must not be null"); //$NON-NLS-1$
|
||||
}
|
||||
int[] binaryGuid = new int[GUID.GUID_LENGTH];
|
||||
for (int i = 0; i < binaryGuid.length; i++) {
|
||||
binaryGuid[i] = stream.read();
|
||||
}
|
||||
return new GUID(binaryGuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads 2 bytes from stream and interprets them as UINT16.<br>
|
||||
*
|
||||
* @param stream stream to read from.
|
||||
* @return UINT16 value
|
||||
* @throws IOException on I/O Errors.
|
||||
*/
|
||||
public static int readUINT16(InputStream stream) throws IOException {
|
||||
int result = stream.read();
|
||||
result |= stream.read() << 8;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads 4 bytes from stream and interprets them as UINT32.<br>
|
||||
*
|
||||
* @param stream stream to read from.
|
||||
* @return UINT32 value
|
||||
* @throws IOException on I/O Errors.
|
||||
*/
|
||||
public static long readUINT32(InputStream stream) throws IOException {
|
||||
long result = 0;
|
||||
for (int i = 0; i <= 24; i += 8) {
|
||||
// Warning, always cast to long here. Otherwise it will be
|
||||
// shifted as int, which may produce a negative value, which will
|
||||
// then be extended to long and assign the long variable a negative
|
||||
// value.
|
||||
result |= (long) stream.read() << i;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads long as little endian.
|
||||
*
|
||||
* @param stream Data source
|
||||
* @return long value
|
||||
* @throws IOException read error, or eof is reached before long is completed
|
||||
*/
|
||||
public static long readUINT64(InputStream stream) throws IOException {
|
||||
long result = 0;
|
||||
for (int i = 0; i <= 56; i += 8) {
|
||||
// Warning, always cast to long here. Otherwise it will be
|
||||
// shifted as int, which may produce a negative value, which will
|
||||
// then be extended to long and assign the long variable a negative
|
||||
// value.
|
||||
result |= (long) stream.read() << i;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method reads a UTF-16 encoded String, beginning with a 16-bit value
|
||||
* representing the number of bytes needed. The String is terminated with as
|
||||
* 16-bit ZERO. <br>
|
||||
*
|
||||
* @param stream Input source
|
||||
* @return read String.
|
||||
* @throws IOException read errors.
|
||||
*/
|
||||
public static String readUTF16LEStr(InputStream stream) throws IOException {
|
||||
int strLen = readUINT16(stream);
|
||||
byte[] buf = new byte[strLen];
|
||||
int read = stream.read(buf);
|
||||
if (read == strLen || (strLen == 0 && read == -1)) {
|
||||
/*
|
||||
* Check on zero termination
|
||||
*/
|
||||
if (buf.length >= 2) {
|
||||
if (buf[buf.length - 1] == 0 && buf[buf.length - 2] == 0) {
|
||||
byte[] copy = new byte[buf.length - 2];
|
||||
System.arraycopy(buf, 0, copy, 0, buf.length - 2);
|
||||
buf = copy;
|
||||
}
|
||||
}
|
||||
return new String(buf, AsfHeader.ASF_CHARSET.name());
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Invalid Data for current interpretation"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given value as UINT16 into the stream.
|
||||
*
|
||||
* @param number value to write.
|
||||
* @param out stream to write into.
|
||||
* @throws IOException On I/O errors
|
||||
*/
|
||||
public static void writeUINT16(int number, OutputStream out)
|
||||
throws IOException {
|
||||
if (number < 0) {
|
||||
throw new IllegalArgumentException("positive value expected."); //$NON-NLS-1$
|
||||
}
|
||||
byte[] toWrite = new byte[2];
|
||||
for (int i = 0; i <= 8; i += 8) {
|
||||
toWrite[i / 8] = (byte) ((number >> i) & 0xFF);
|
||||
}
|
||||
out.write(toWrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given value as UINT32 into the stream.
|
||||
*
|
||||
* @param number value to write.
|
||||
* @param out stream to write into.
|
||||
* @throws IOException On I/O errors
|
||||
*/
|
||||
public static void writeUINT32(long number, OutputStream out)
|
||||
throws IOException {
|
||||
if (number < 0) {
|
||||
throw new IllegalArgumentException("positive value expected."); //$NON-NLS-1$
|
||||
}
|
||||
byte[] toWrite = new byte[4];
|
||||
for (int i = 0; i <= 24; i += 8) {
|
||||
toWrite[i / 8] = (byte) ((number >> i) & 0xFF);
|
||||
}
|
||||
out.write(toWrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given value as UINT64 into the stream.
|
||||
*
|
||||
* @param number value to write.
|
||||
* @param out stream to write into.
|
||||
* @throws IOException On I/O errors
|
||||
*/
|
||||
public static void writeUINT64(long number, OutputStream out)
|
||||
throws IOException {
|
||||
if (number < 0) {
|
||||
throw new IllegalArgumentException("positive value expected."); //$NON-NLS-1$
|
||||
}
|
||||
byte[] toWrite = new byte[8];
|
||||
for (int i = 0; i <= 56; i += 8) {
|
||||
toWrite[i / 8] = (byte) ((number >> i) & 0xFF);
|
||||
}
|
||||
out.write(toWrite);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
|
||||
|
||||
<html long="en">
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Utility classes for data components of the Microsoft Advanced Systems Format header.
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
<!-- package.html by Gary McGath -->
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2003-2005 Raphaël Slinckx <raphael@slinckx.net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.exceptions;
|
||||
|
||||
/**
|
||||
* This exception is thrown if an audio file cannot be read.<br>
|
||||
* Causes may be invalid data or IO errors.
|
||||
*
|
||||
* @author Raphaël Slinckx
|
||||
*/
|
||||
public class CannotReadException extends Exception {
|
||||
/**
|
||||
* Creates an instance.
|
||||
*/
|
||||
public CannotReadException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public CannotReadException(Throwable ex) {
|
||||
super(ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param message The message.
|
||||
*/
|
||||
public CannotReadException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param message The error message.
|
||||
* @param cause The throwable causing this exception.
|
||||
*/
|
||||
public CannotReadException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package org.jaudiotagger.audio.exceptions;
|
||||
|
||||
/**
|
||||
* This exception should be thrown idf it appears the file is a video file, jaudiotagger only supports audio
|
||||
* files.
|
||||
*/
|
||||
public class CannotReadVideoException extends CannotReadException {
|
||||
/**
|
||||
* Creates an instance.
|
||||
*/
|
||||
public CannotReadVideoException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public CannotReadVideoException(Throwable ex) {
|
||||
super(ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param message The message.
|
||||
*/
|
||||
public CannotReadVideoException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param message The error message.
|
||||
* @param cause The throwable causing this exception.
|
||||
*/
|
||||
public CannotReadVideoException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2003-2005 Raphaël Slinckx <raphael@slinckx.net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.exceptions;
|
||||
|
||||
/**
|
||||
* This exception is thrown if the writing process of an audio file failed.
|
||||
*
|
||||
* @author Rapha<EFBFBD>l Slinckx
|
||||
*/
|
||||
public class CannotWriteException extends Exception {
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @see Exception#Exception()
|
||||
*/
|
||||
public CannotWriteException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @param message
|
||||
* @see Exception#Exception(java.lang.String)
|
||||
*/
|
||||
public CannotWriteException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @param message
|
||||
* @param cause
|
||||
* @see Exception#Exception(java.lang.String, java.lang.Throwable)
|
||||
*/
|
||||
public CannotWriteException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @param cause
|
||||
* @see Exception#Exception(java.lang.Throwable)
|
||||
*/
|
||||
public CannotWriteException(Throwable cause) {
|
||||
super(cause);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
/**
|
||||
* @author : Paul Taylor
|
||||
* <p/>
|
||||
* Version @version:$Id: InvalidAudioFrameException.java 345 2007-08-07 16:14:03Z paultaylor $
|
||||
* Date :${DATE}
|
||||
* <p/>
|
||||
* Jaikoz Copyright Copyright (C) 2003 -2005 JThink Ltd
|
||||
*/
|
||||
package org.jaudiotagger.audio.exceptions;
|
||||
|
||||
/**
|
||||
* Thrown if portion of file thought to be an AudioFrame is found to not be.
|
||||
*/
|
||||
public class InvalidAudioFrameException extends Exception {
|
||||
public InvalidAudioFrameException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package org.jaudiotagger.audio.exceptions;
|
||||
|
||||
/**
|
||||
* Thrown if when trying to read box id the length doesn't make any sense
|
||||
*/
|
||||
public class InvalidBoxHeaderException extends RuntimeException {
|
||||
public InvalidBoxHeaderException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2003-2005 Christian Laireiter <liree@web.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.exceptions;
|
||||
|
||||
|
||||
/**
|
||||
* This exception is thrown if a
|
||||
* {@link org.jaudiotagger.audio.generic.AudioFileModificationListener} wants to
|
||||
* prevent the "e;entagged audio library"e; from actually finishing its
|
||||
* operation.<br>
|
||||
* This exception can be used in all methods but
|
||||
* {@link org.jaudiotagger.audio.generic.AudioFileModificationListener#fileOperationFinished(java.io.File)}.
|
||||
*
|
||||
* @author Christian Laireiter
|
||||
*/
|
||||
public class ModifyVetoException extends Exception {
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*/
|
||||
public ModifyVetoException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @param message
|
||||
* @see Exception#Exception(java.lang.String)
|
||||
*/
|
||||
public ModifyVetoException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @param message
|
||||
* @param cause
|
||||
* @see Exception#Exception(java.lang.String, java.lang.Throwable)
|
||||
*/
|
||||
public ModifyVetoException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* (overridden)
|
||||
*
|
||||
* @param cause
|
||||
* @see Exception#Exception(java.lang.Throwable)
|
||||
*/
|
||||
public ModifyVetoException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package org.jaudiotagger.audio.exceptions;
|
||||
|
||||
/**
|
||||
* Thrown if when trying to read box id just finds nulls
|
||||
* Normally an error, but if occurs at end of file we allow it
|
||||
*/
|
||||
public class NullBoxIdException extends RuntimeException {
|
||||
public NullBoxIdException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
/**
|
||||
* @author : Paul Taylor
|
||||
* @author : Eric Farng
|
||||
* <p>
|
||||
* Version @version:$Id: ReadOnlyFileException.java 345 2007-08-07 16:14:03Z paultaylor $
|
||||
* <p>
|
||||
* MusicTag Copyright (C)2003,2004
|
||||
* <p>
|
||||
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
* General Public License as published by the Free Software Foundation; either version 2.1 of the License,
|
||||
* or (at your option) any later version.
|
||||
* <p>
|
||||
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Lesser General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU Lesser General Public License along with this library; if not,
|
||||
* you can get a copy from http://www.opensource.org/licenses/lgpl-license.php or write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.exceptions;
|
||||
|
||||
/**
|
||||
* This is the exception when try and access a read only file
|
||||
*/
|
||||
public class ReadOnlyFileException extends Exception {
|
||||
/**
|
||||
* Creates a new ReadOnlyException datatype.
|
||||
*/
|
||||
public ReadOnlyFileException() {
|
||||
}
|
||||
|
||||
public ReadOnlyFileException(Throwable ex) {
|
||||
super(ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ReadOnlyException datatype.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public ReadOnlyFileException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public ReadOnlyFileException(String msg, Throwable ex) {
|
||||
super(msg, ex);
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package org.jaudiotagger.audio.exceptions;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Should be thrown when unable to create a file when it is expected it should be creatable. For example because
|
||||
* you dont have permission to write to the folder that it is in.
|
||||
*/
|
||||
public class UnableToCreateFileException extends IOException {
|
||||
public UnableToCreateFileException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package org.jaudiotagger.audio.exceptions;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Should be thrown when unable to modify a file when it is expected it should bemodifiable. For example because
|
||||
* you dont have permission to modify files in the folder that it is in.
|
||||
*/
|
||||
public class UnableToModifyFileException extends IOException {
|
||||
public UnableToModifyFileException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package org.jaudiotagger.audio.exceptions;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Should be thrown when unable to rename a file when it is expected it should rename. For example could occur on Vista
|
||||
* because you do not have Special Permission 'Delete' set to Denied.
|
||||
*/
|
||||
public class UnableToRenameFileException extends IOException {
|
||||
public UnableToRenameFileException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
|
||||
|
||||
<html long="en">
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Exceptions defined for Jaudiotagger.
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
<!-- package.html by Gary McGath -->
|
||||
<!-- Put @see and @since tags down here. -->
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,15 +0,0 @@
|
|||
package org.jaudiotagger.audio.flac;
|
||||
|
||||
import org.jaudiotagger.audio.generic.GenericAudioHeader;
|
||||
|
||||
public class FlacAudioHeader extends GenericAudioHeader {
|
||||
private String md5;
|
||||
|
||||
public String getMd5() {
|
||||
return md5;
|
||||
}
|
||||
|
||||
public void setMd5(String md5) {
|
||||
this.md5 = md5;
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2003-2005 Raphaël Slinckx <raphael@slinckx.net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.flac;
|
||||
|
||||
import org.jaudiotagger.audio.exceptions.CannotReadException;
|
||||
import org.jaudiotagger.audio.generic.AudioFileReader;
|
||||
import org.jaudiotagger.audio.generic.GenericAudioHeader;
|
||||
import org.jaudiotagger.tag.Tag;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
/**
|
||||
* Read encoding and tag info for Flac file (open source lossless encoding)
|
||||
*/
|
||||
public class FlacFileReader extends AudioFileReader {
|
||||
|
||||
private FlacInfoReader ir = new FlacInfoReader();
|
||||
private FlacTagReader tr = new FlacTagReader();
|
||||
|
||||
protected GenericAudioHeader getEncodingInfo(RandomAccessFile raf) throws CannotReadException, IOException {
|
||||
return ir.read(raf);
|
||||
}
|
||||
|
||||
protected Tag getTag(RandomAccessFile raf) throws CannotReadException, IOException {
|
||||
return tr.read(raf);
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* Entagged Audio Tag library
|
||||
* Copyright (c) 2003-2005 Raphaël Slinckx <raphael@slinckx.net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jaudiotagger.audio.flac;
|
||||
|
||||
import org.jaudiotagger.audio.exceptions.CannotWriteException;
|
||||
import org.jaudiotagger.audio.generic.AudioFileWriter;
|
||||
import org.jaudiotagger.tag.Tag;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
|
||||
/**
|
||||
* Write/delete tag info for Flac file (opensource lossless encoding)
|
||||
*/
|
||||
public class FlacFileWriter extends AudioFileWriter {
|
||||
|
||||
private FlacTagWriter tw = new FlacTagWriter();
|
||||
|
||||
protected void writeTag(Tag tag, RandomAccessFile raf, RandomAccessFile rafTemp) throws CannotWriteException, IOException {
|
||||
tw.write(tag, raf, rafTemp);
|
||||
}
|
||||
|
||||
protected void deleteTag(RandomAccessFile raf, RandomAccessFile tempRaf) throws CannotWriteException, IOException {
|
||||
tw.delete(raf, tempRaf);
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue