/** Reads a WAVE Chunk. */ protected boolean readChunk(RepInfo info) throws IOException { Chunk chunk = null; ChunkHeader chunkh = new ChunkHeader(this, info); if (!chunkh.readHeader(_dstream)) { return false; } int chunkSize = (int) chunkh.getSize(); bytesRemaining -= chunkSize + 8; if (bytesRemaining < 0) { info.setMessage(new ErrorMessage("Invalid chunk size", _nByte)); return false; } String id = chunkh.getID(); if ("fmt ".equals(id)) { if (formatChunkSeen) { dupChunkError(info, "Format"); } chunk = new FormatChunk(this, chunkh, _dstream); formatChunkSeen = true; } else if ("data".equals(id)) { if (dataChunkSeen) { dupChunkError(info, "Data"); } chunk = new DataChunk(this, chunkh, _dstream); dataChunkSeen = true; } else if ("fact".equals(id)) { chunk = new FactChunk(this, chunkh, _dstream); factChunkSeen = true; // Are multiple 'fact' chunks allowed? } else if ("note".equals(id)) { chunk = new NoteChunk(this, chunkh, _dstream); // Multiple note chunks are allowed } else if ("labl".equals(id)) { chunk = new LabelChunk(this, chunkh, _dstream); // Multiple label chunks are allowed } else if ("list".equals(id)) { chunk = new AssocDataListChunk(this, chunkh, _dstream, info); // Are multiple chunks allowed? Who knows? } else if ("LIST".equals(id)) { chunk = new ListInfoChunk(this, chunkh, _dstream, info); // Multiple list chunks must be OK, since there can // be different types, e.g., an INFO list and an exif list. } else if ("smpl".equals(id)) { chunk = new SampleChunk(this, chunkh, _dstream); // Multiple sample chunks are allowed -- I think } else if ("inst".equals(id)) { if (instrumentChunkSeen) { dupChunkError(info, "Instrument"); } chunk = new InstrumentChunk(this, chunkh, _dstream); // Only one instrument chunk is allowed instrumentChunkSeen = true; } else if ("mext".equals(id)) { if (mpegChunkSeen) { dupChunkError(info, "MPEG"); } chunk = new MpegChunk(this, chunkh, _dstream); // I think only one MPEG chunk is allowed mpegChunkSeen = true; } else if ("cart".equals(id)) { if (cartChunkSeen) { dupChunkError(info, "Cart"); } chunk = new CartChunk(this, chunkh, _dstream); cartChunkSeen = true; } else if ("bext".equals(id)) { if (broadcastExtChunkSeen) { dupChunkError(info, "Broadcast Audio Extension"); } chunk = new BroadcastExtChunk(this, chunkh, _dstream); broadcastExtChunkSeen = true; } else if ("levl".equals(id)) { if (peakChunkSeen) { dupChunkError(info, "Peak Envelope"); } chunk = new PeakEnvelopeChunk(this, chunkh, _dstream); peakChunkSeen = true; } else if ("link".equals(id)) { if (linkChunkSeen) { dupChunkError(info, "Link"); } chunk = new LinkChunk(this, chunkh, _dstream); linkChunkSeen = true; } else if ("cue ".equals(id)) { if (cueChunkSeen) { dupChunkError(info, "Cue"); } chunk = new CueChunk(this, chunkh, _dstream); cueChunkSeen = true; } else { info.setMessage(new InfoMessage("Chunk type '" + id + "' ignored", _nByte)); } if (chunk != null) { if (!chunk.readChunk(info)) { return false; } } else { // Other chunk types are legal, just skip over them skipBytes(_dstream, chunkSize, this); } if ((chunkSize & 1) != 0) { // Must come out to an even byte boundary skipBytes(_dstream, 1, this); --bytesRemaining; } return true; }
/* Factor out the reporting of duplicate chunks. */ protected void dupChunkError(RepInfo info, String chunkName) { info.setMessage(new ErrorMessage("Multiple " + chunkName + " Chunks not permitted", _nByte)); info.setValid(false); }
/** * Parses the content of a purported WAVE digital object and stores the results in RepInfo. * * @param stream An InputStream, positioned at its beginning, which is generated from the object * to be parsed * @param info A fresh RepInfo object which will be modified to reflect the results of the parsing * @param parseIndex Must be 0 in first call to <code>parse</code>. If <code>parse</code> returns * a nonzero value, it must be called again with <code>parseIndex</code> equal to that return * value. */ public int parse(InputStream stream, RepInfo info, int parseIndex) throws IOException { initParse(); info.setFormat(_format[0]); info.setMimeType(_mimeType[0]); info.setModule(this); _aesMetadata.setPrimaryIdentifier(info.getUri()); if (info.getURLFlag()) { _aesMetadata.setOtherPrimaryIdentifierType("URI"); } else { _aesMetadata.setPrimaryIdentifierType(AESAudioMetadata.FILE_NAME); } /* We may have already done the checksums while converting a temporary file. */ _ckSummer = null; if (_je != null && _je.getChecksumFlag() && info.getChecksum().size() == 0) { _ckSummer = new Checksummer(); _cstream = new ChecksumInputStream(stream, _ckSummer); _dstream = getBufferedDataStream(_cstream, _je != null ? _je.getBufferSize() : 0); } else { _dstream = getBufferedDataStream(stream, _je != null ? _je.getBufferSize() : 0); } try { // Check the start of the file for the right opening bytes for (int i = 0; i < 4; i++) { int ch = readUnsignedByte(_dstream, this); if (ch != sigByte[i]) { info.setMessage(new ErrorMessage("Document does not start with RIFF chunk", 0)); info.setWellFormed(false); return 0; } } /* If we got this far, take note that the signature is OK. */ info.setSigMatch(_name); // Get the length of the Form chunk. This includes all // the subsequent chunks in the file, but excludes the // header ("FORM" and the length itself). bytesRemaining = readUnsignedInt(_dstream); // Read the file type. String typ = read4Chars(_dstream); bytesRemaining -= 4; if (!"WAVE".equals(typ)) { info.setMessage(new ErrorMessage("File type in RIFF header is not WAVE", _nByte)); info.setWellFormed(false); return 0; } while (bytesRemaining > 0) { if (!readChunk(info)) { break; } } } catch (EOFException e) { info.setWellFormed(false); info.setMessage(new ErrorMessage("Unexpected end of file", _nByte)); return 0; } // Set duration from number of samples and rate. if (numSamples > 0) { // _aesMetadata.setDuration((double) numSamples / sampleRate); _aesMetadata.setDuration(numSamples); } // Add note and label properties, if there's anything // to report. if (!_labels.isEmpty()) { _propList.add(new Property("Labels", PropertyType.PROPERTY, PropertyArity.LIST, _labels)); } if (!_labeledText.isEmpty()) { _propList.add( new Property("LabeledText", PropertyType.PROPERTY, PropertyArity.LIST, _labeledText)); } if (!_notes.isEmpty()) { _propList.add(new Property("Notes", PropertyType.PROPERTY, PropertyArity.LIST, _notes)); } if (!_samples.isEmpty()) { _propList.add(new Property("Samples", PropertyType.PROPERTY, PropertyArity.LIST, _samples)); } if (_exifInfo != null) { _propList.add(_exifInfo.buildProperty()); } if (!formatChunkSeen) { info.setMessage(new ErrorMessage("No Format Chunk")); info.setWellFormed(false); return 0; } /* This file looks OK. */ if (_ckSummer != null) { /* We may not have actually hit the end of file. If we're calculating * checksums on the fly, we have to read and discard whatever is * left, so it will get checksummed. */ for (; ; ) { try { int n = skipBytes(_dstream, 2048, this); if (n == 0) { break; } } catch (Exception e) { break; } } info.setSize(_cstream.getNBytes()); info.setChecksum(new Checksum(_ckSummer.getCRC32(), ChecksumType.CRC32)); String value = _ckSummer.getMD5(); if (value != null) { info.setChecksum(new Checksum(value, ChecksumType.MD5)); } if ((value = _ckSummer.getSHA1()) != null) { info.setChecksum(new Checksum(value, ChecksumType.SHA1)); } } info.setProperty(_metadata); // Indicate satisfied profiles. if (flagPCMWaveFormat) { info.setProfile("PCMWAVEFORMAT"); } if (flagWaveFormatEx) { info.setProfile("WAVEFORMATEX"); } if (flagWaveFormatExtensible) { info.setProfile("WAVEFORMATEXTENSIBLE"); } if (flagBroadcastWave) { // Need to do some additional checks. if (!broadcastExtChunkSeen) { flagBroadcastWave = false; } if (compressionCode == FormatChunk.WAVE_FORMAT_MPEG) { if (!broadcastExtChunkSeen || !factChunkSeen) { flagBroadcastWave = false; } } if (flagBroadcastWave) { String prof = null; switch (broadcastVersion) { case 0: prof = "Broadcast Wave Version 0"; break; case 1: prof = "Broadcast Wave Version 1"; break; // Other versions are unknown at this time } if (prof != null) { info.setProfile(prof); } } } return 0; }