/** 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; }
/** * Reads a chunk and puts a BroadcastAudioExtension Property into the RepInfo object. * * @return <code>false</code> if the chunk is structurally invalid, otherwise <code>true</code> */ public boolean readChunk(RepInfo info) throws IOException { WaveModule module = (WaveModule) _module; byte[] buf256 = new byte[256]; ModuleBase.readByteBuf(_dstream, buf256, module); String description = byteBufString(buf256); byte[] buf32 = new byte[32]; ModuleBase.readByteBuf(_dstream, buf32, module); String originator = byteBufString(buf32); ModuleBase.readByteBuf(_dstream, buf32, module); String originatorRef = byteBufString(buf32); byte[] buf10 = new byte[10]; ModuleBase.readByteBuf(_dstream, buf10, module); String originationDate = byteBufString(buf10); byte[] buf8 = new byte[8]; ModuleBase.readByteBuf(_dstream, buf8, module); String originationTime = byteBufString(buf8); // TimeReference is stored as a 64-bit little-endian // number -- I think long timeReference = module.readSignedLong(_dstream); int version = module.readUnsignedShort(_dstream); module.setBroadcastVersion(version); byte[] smtpe_umid = new byte[64]; ModuleBase.readByteBuf(_dstream, smtpe_umid, module); module.skipBytes(_dstream, 190, module); String codingHistory = ""; if (bytesLeft > 602) { byte[] bufCodingHistory = new byte[(int) bytesLeft - 602]; ModuleBase.readByteBuf(_dstream, bufCodingHistory, module); codingHistory = byteBufString(bufCodingHistory); } // Whew -- we've read the whole thing. Now make that into a // list of Properties. List plist = new ArrayList(20); if (description.length() > 0) { plist.add(new Property("Description", PropertyType.STRING, description)); } if (originator.length() > 0) { plist.add(new Property("Originator", PropertyType.STRING, originator)); } if (originationDate.length() > 0) { plist.add(new Property("OriginationDate", PropertyType.STRING, originationDate)); } if (originationTime.length() > 0) { plist.add(new Property("OriginationTime", PropertyType.STRING, originationTime)); } plist.add(new Property("TimeReference", PropertyType.LONG, new Long(timeReference))); plist.add(new Property("Version", PropertyType.INTEGER, new Integer(version))); plist.add(new Property("UMID", PropertyType.BYTE, PropertyArity.ARRAY, smtpe_umid)); if (codingHistory.length() > 0) { plist.add(new Property("CodingHistory", PropertyType.STRING, codingHistory)); } module.addWaveProperty( new Property("BroadcastAudioExtension", PropertyType.PROPERTY, PropertyArity.LIST, plist)); // set time reference in AES metadata set @author David Ackerman AESAudioMetadata aes = module.getAESMetadata(); aes.setStartTime(timeReference); return true; }
/** * Marks the first sample offset as the current byte position, if it hasn't already been marked. */ public void markFirstSampleOffset() { if (!firstSampleOffsetMarked) { firstSampleOffsetMarked = true; _aesMetadata.setFirstSampleOffset(_nByte); } }
/** * 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; }