/** * JHOVE2 - Next-generation architecture for format-aware characterization * * Copyright (c) 2009 by The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of the University of California/California Digital * Library, Ithaka Harbors/Portico, or Stanford University, nor the names of * its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package org.jhove2.module.format.wave; import java.io.EOFException; import java.io.IOException; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.jhove2.annotation.ReportableProperty; import org.jhove2.core.JHOVE2; import org.jhove2.core.JHOVE2Exception; import org.jhove2.core.Message; import org.jhove2.core.Message.Context; import org.jhove2.core.Message.Severity; import org.jhove2.core.format.Format; import org.jhove2.core.io.Input; import org.jhove2.core.source.MeasurableSource; import org.jhove2.core.source.Source; import org.jhove2.module.format.BaseFormatModule; import org.jhove2.module.format.Validator; import org.jhove2.module.format.riff.Chunk; import org.jhove2.module.format.riff.ChunkFactory; import org.jhove2.persist.FormatModuleAccessor; import com.sleepycat.persist.model.Persistent; /** WAVE (waveform audio file format) module. * * @author slabrams */ @Persistent public class WAVEModule extends BaseFormatModule implements Validator { /** WAVE module version identifier. */ public static final String VERSION = "2.0.0"; /** WAVE module release date. */ public static final String RELEASE = "2010-09-10"; /** WAVE module rights statement. */ public static final String RIGHTS = "Copyright 2010 by The Regents of the University of California" + "Available under the terms of the BSD license."; /** Module validation coverage. */ public static final Coverage COVERAGE = Coverage.Inclusive; /** Chunk factory. */ protected ChunkFactory chunkFactory; /** WAVE chunks. */ protected List<Chunk> chunks; /** WAVE validation status. */ protected Validity isValid; /** Duplicate chunk message. */ protected Message duplicateChunkMessage; /** File truncated message: the RIFF chunk size is greater than the file * size. */ protected Message fileTruncatedMessage; /** Format chunk not before data chunk message. */ protected Message formatChunkNotBeforeDataChunkMessage; /** Missing required chunk message. */ protected Message missingRequiredDataChunkMessage; /** Missing required fact chunk message. */ protected Message missingRequiredFactChunkMessage; /** Missing required format chunk message. */ protected Message missingRequiredFormatChunkMessage; /** RIFF chunk size smaller than file size message. * This is considered an error according to the US Federal Agencies * Digitization Guidelines Initiative (FADGI). */ protected Message riffChunkSmallerThanFileMessage; /** Instantiate a new <code>WAVEModule</code>. * @param format WAVE format * @param formatModuleAccessor peristence manager */ public WAVEModule(Format format, FormatModuleAccessor formatModuleAccessor) { super(VERSION, RELEASE, RIGHTS, format, formatModuleAccessor); this.chunks = new ArrayList<Chunk>(); this.isValid = Validity.Undetermined; } @SuppressWarnings("unused") private WAVEModule(){ this(null, null); } /** * Parse a WAVE source unit. * * @param jhove2 * JHOVE2 framework * @param source * WAVE source unit * @param input WAVE source input * @return Number of bytes consumed * @throws EOFException * If End-of-File is reached reading the source unit * @throws IOException * If an I/O exception is raised reading the source unit * @throws JHOVE2Exception * @see org.jhove2.module.format.Parser#parse(org.jhove2.core.JHOVE2, * org.jhove2.core.source.Source, org.jhove2.core.io.Input) */ @Override public long parse(JHOVE2 jhove2, Source source, Input input) throws EOFException, IOException, JHOVE2Exception { long consumed = 0L; this.isValid = Validity.True; input.setByteOrder(ByteOrder.LITTLE_ENDIAN); input.setPosition(((MeasurableSource) source).getStartingOffset()); StringBuffer sb = new StringBuffer(4); for (int i=0; i<4; i++) { short b = input.readUnsignedByte(); sb.append((char) b); } Chunk chunk = this.chunkFactory.getChunk(sb.toString()); consumed += chunk.parse(jhove2, source, input); this.chunks.add(chunk); return consumed; } /** Validate the WAVE source unit. * @param jhove2 JHOVE2 framework * @param source WAVE source unit * @param input WAVE source input * @see org.jhove2.module.format.Validator#validate(org.jhove2.core.JHOVE2, org.jhove2.core.source.Source, org.jhov2.core.io.Input) */ @Override public Validity validate(JHOVE2 jhove2, Source source, Input input) throws JHOVE2Exception { /* Only FILR, FLLR, PAD_, JUNK, and JUNQ chunks can have more than * one instance. */ Map<String, Integer> map = new HashMap<String, Integer>(); /* A valid WAVE must have a format chunk followed by a data chunk. */ Iterator<Chunk> it = this.chunks.iterator(); while (it.hasNext()) { Chunk ch = it.next(); String id = ch.getIdentifier(); if (id.equals("RIFF")) { Integer c = map.get(id); int ct = (c == null ? 0 : c.intValue()); map.put(id, ct+1); int formatCategory = 0; boolean hasDataChunk = false; boolean hasFactChunk = false; boolean hasFormatChunk = false; Object [] args = null; List<Chunk> children = ch.getChildChunks(); Iterator<Chunk> iter = children.iterator(); while (iter.hasNext()) { Chunk chunk = iter.next(); id = chunk.getIdentifier(); c = map.get(id); ct = (c == null ? 0 : c.intValue()); map.put(id, ct+1); if (id.equals("data")) { hasDataChunk = true; /* Data chunk must come after the format chunk. */ if (!hasFormatChunk) { this.isValid = Validity.False; this.formatChunkNotBeforeDataChunkMessage = new Message(Severity.ERROR, Context.OBJECT, "org.jhove2.module.format.wave.WaveModule.formatChunkNotBeforeDataChunk", args, jhove2.getConfigInfo()); } } else if (id.equals("fact")) { hasFactChunk = true; } else if (id.equals("fmt ")) { hasFormatChunk = true; formatCategory = ((FormatChunk) chunk).getFormatCategory_raw(); } /* Check if RIFF chunk size (+8 to account for the ID and * size fields) is equal to file size. */ long chunkSize = ch.getSize(); long fileSize = ((MeasurableSource) source).getSize(); if ((chunkSize+8) < fileSize) { this.isValid = Validity.False; Object [] a = new Object [] {chunkSize, fileSize}; this.riffChunkSmallerThanFileMessage = new Message(Severity.ERROR, Context.OBJECT, "org.jhove2.module.format.wave.WaveModule.riffChunkSmallerThanFile", a, jhove2.getConfigInfo()); } /* Check if the file has been truncated, i.e. the RIFF * chunk size is greater than the file size. */ if ((chunkSize+8) > fileSize) { this.isValid = Validity.False; Object [] a = new Object [] {chunkSize, fileSize}; this.fileTruncatedMessage = new Message(Severity.ERROR, Context.OBJECT, "org.jhove2.module.format.wave.WaveModule.fileTruncated", a, jhove2.getConfigInfo()); } } /* Format and data chunks are required. */ if (!hasDataChunk) { this.isValid = Validity.False; this.missingRequiredDataChunkMessage = new Message(Severity.ERROR, Context.OBJECT, "org.jhove2.module.format.wave.WaveModule.missingRequiredDataChunk", args, jhove2.getConfigInfo()); } if (!hasFormatChunk) { this.isValid = Validity.False; this.missingRequiredFormatChunkMessage = new Message(Severity.ERROR, Context.OBJECT, "org.jhove2.module.format.wave.WaveModule.missingRequiredFormatChunk", args, jhove2.getConfigInfo()); } /* Fact chunk is required for non-PCM data. */ if (formatCategory != FormatChunk.WAVE_FORMAT_PCM && !hasFactChunk) { this.isValid = Validity.False; this.missingRequiredFactChunkMessage = new Message(Severity.ERROR, Context.OBJECT, "org.jhove2.module.format.wave.WaveModule.missingRequiredFactChunk", args, jhove2.getConfigInfo()); } break; } } Set<String> keys = map.keySet(); Iterator<String> iter = keys.iterator(); while (iter.hasNext()) { String key = iter.next(); int ct = map.get(key); if (ct > 1 && !key.equals("FILR") && !key.equals("FLLR") && !key.equals("PAD_") && !key.equals("JUNK") && !key.equals("JUNQ")) { this.isValid = Validity.False; Object [] args = new Object [] {key}; this.duplicateChunkMessage = new Message(Severity.ERROR, Context.OBJECT, "org.jhove2.module.format.wave.WaveModule.duplicateChunk", args, jhove2.getConfigInfo()); } } return this.isValid(); } /** Get chunk factory. * @return Chunk factory */ public ChunkFactory getChunkFactory() { return this.chunkFactory; } /** Get chunks. * @return Chunks */ @ReportableProperty(order=1, value="Chunks.") public List<Chunk> getChunks() { return this.chunks; } /** Get module coverage. * @return Module coverage * @see org.jhove2.module.format.Validator#getCoverage() */ @Override public Coverage getCoverage() { return COVERAGE; } /** Get duplicate chunk message. * @return Duplicate chunk message */ @ReportableProperty(order=27, value="Duplicate chunk message.") public Message getDuplicateChunkMessage() { return this.duplicateChunkMessage; } /** Get file truncated message. * @return File truncated message */ @ReportableProperty(order=26, value="File truncated message.") public Message getFileTruncatedMessage() { return this.fileTruncatedMessage; } /** Get format chunk not before data chunk message. * @return Format chunk not before data chunk message */ @ReportableProperty(order=21, value="Format chunk does not appear before the data chunk.") public Message getFormatChunkNotBeforeDataChunkMessage() { return this.formatChunkNotBeforeDataChunkMessage; } /** Get missing required data chunk message. * @return Missing required data chunk message */ @ReportableProperty(order=23, value="Missing required data chunk message.") public Message getMissingRequiredDataChunkMessage() { return this.missingRequiredDataChunkMessage; } /** Get missing required fact chunk message. * @return Missing required fact chunk message. */ @ReportableProperty(order=24, value="Missing required fact chunk message.") public Message getMissingRequiredFactChunkMessage() { return this.missingRequiredFactChunkMessage; } /** Get missing required format chunk message. * @return Missing required format chunk message */ @ReportableProperty(order=22, value="Missing required format chunk message.") public Message getMissingRequiredFormatChunkMessage() { return this.missingRequiredFormatChunkMessage; } /** Get RIFF chunk smaller than file size message. * @return RIFF chunk smaller than file size message */ @ReportableProperty(order=25, value="RIFF chunk smaller than file size message.") public Message getRIFFChunkSmallerThanFileMessage() { return this.riffChunkSmallerThanFileMessage; } /** Get validation status. * @return Validation status * @see org.jhove2.module.format.Validator#isValid() */ @Override public Validity isValid() { return this.isValid; } /** Set chunk factory. * @param factory Chunk factory */ public void setChunkFactory(ChunkFactory factory) { this.chunkFactory = factory; } }