/** Initializes the state of the module for parsing. */ protected void initParse() { super.initParse(); _propList = new LinkedList(); _notes = new LinkedList(); _labels = new LinkedList(); _labeledText = new LinkedList(); _samples = new LinkedList(); firstSampleOffsetMarked = false; numSamples = 0; _metadata = new Property("WAVEMetadata", PropertyType.PROPERTY, PropertyArity.LIST, _propList); _aesMetadata = new AESAudioMetadata(); _aesMetadata.setByteOrder(AESAudioMetadata.LITTLE_ENDIAN); _aesMetadata.setAnalogDigitalFlag("FILE_DIGITAL"); _aesMetadata.setFormat("WAVE"); _aesMetadata.setUse("OTHER", "JHOVE_validation"); _aesMetadata.setDirection("NONE"); _propList.add(new Property("AESAudioMetadata", PropertyType.AESAUDIOMETADATA, _aesMetadata)); // Most chunk types are allowed to occur only once, // and a few must occur exactly once. // Clear flags for whether they have been seen. formatChunkSeen = false; dataChunkSeen = false; instrumentChunkSeen = false; cartChunkSeen = false; mpegChunkSeen = false; broadcastExtChunkSeen = false; peakChunkSeen = false; linkChunkSeen = false; cueChunkSeen = false; // Initialize profile flags flagPCMWaveFormat = false; flagWaveFormatEx = false; flagWaveFormatExtensible = false; flagBroadcastWave = false; }
/** * General function for adding a property with a 32-bit value, with two arrays of Strings to * interpret 0 and 1 values as a bitmask. * * @param val The bitmask * @param name The name for the Property * @param oneValueNames Array of names to use for '1' values * @param zeroValueNames Array of names to use for '0' values */ public Property buildBitmaskProperty( int val, String name, String[] oneValueNames, String[] zeroValueNames) { if (_je != null && _je.getShowRawFlag()) { return new Property(name, PropertyType.INTEGER, new Integer(val)); } else { List slist = new LinkedList(); try { for (int i = 0; i < oneValueNames.length; i++) { String s = null; if ((val & (1 << i)) != 0) { s = oneValueNames[i]; } else { s = zeroValueNames[i]; } if (s != null && s.length() > 0) { slist.add(s); } } } catch (Exception e) { return null; } return new Property(name, PropertyType.STRING, PropertyArity.LIST, slist); } }
/** Adds a Note string */ public void addNote(Property p) { _notes.add(p); }
/** Adds the ListInfo property, which is a List of String Properties. */ public void addListInfo(List l) { _propList.add(new Property("ListInfo", PropertyType.PROPERTY, PropertyArity.LIST, l)); }
/** Adds a Sample property */ public void addSample(Property p) { _samples.add(p); }
/** Adds a LabeledText property */ public void addLabeledText(Property p) { _labeledText.add(p); }
/** Adds a Label property */ public void addLabel(Property p) { _labels.add(p); }
/** Adds a Property to the WAVE metadata. */ public void addWaveProperty(Property prop) { _propList.add(prop); }
/** * 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; }