/** * Reads a chunk and puts an Instrument 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 { AiffModule module = (AiffModule) _module; int baseNote = ModuleBase.readUnsignedByte(_dstream, module); int detune = ModuleBase.readSignedByte(_dstream, module); int lowNote = ModuleBase.readUnsignedByte(_dstream, module); int highNote = ModuleBase.readUnsignedByte(_dstream, module); int lowVelocity = ModuleBase.readUnsignedByte(_dstream, module); int highVelocity = ModuleBase.readUnsignedByte(_dstream, module); int gain = module.readSignedShort(_dstream); Loop sustainLoop = readLoop(module); Loop releaseLoop = readLoop(module); List propList = new ArrayList(9); propList.add(new Property("BaseNote", PropertyType.INTEGER, new Integer(baseNote))); propList.add(new Property("Detune", PropertyType.INTEGER, new Integer(detune))); propList.add(new Property("LowNote", PropertyType.INTEGER, new Integer(lowNote))); propList.add(new Property("HighNote", PropertyType.INTEGER, new Integer(highNote))); propList.add(new Property("LowVelocity", PropertyType.INTEGER, new Integer(lowVelocity))); propList.add(new Property("HighVelocity", PropertyType.INTEGER, new Integer(highVelocity))); propList.add(new Property("Gain", PropertyType.INTEGER, new Integer(gain))); propList.add(sustainLoop.loopProp("SustainLoop")); propList.add(releaseLoop.loopProp("ReleaseLoop")); module.addAiffProperty( new Property("Instrument", PropertyType.PROPERTY, PropertyArity.LIST, propList)); return true; }
/** * Reads an array of strings from the TIFF file. * * @param count Number of strings to read * @param value Offset from which to read */ protected String[] readASCIIArray(long count, long value) throws IOException { _raf.seek(value); int nstrs = 0; List list = new LinkedList(); byte[] buf = new byte[(int) count]; _raf.read(buf); StringBuffer strbuf = new StringBuffer(); for (int i = 0; i < count; i++) { int b = buf[i]; if (b == 0) { list.add(strbuf.toString()); strbuf.setLength(0); } else { strbuf.append((char) b); } } /* We can't use ArrayList.toArray because that returns an Object[], not a String[] ... sigh. */ String[] strs = new String[nstrs]; ListIterator iter = list.listIterator(); for (int i = 0; i < nstrs; i++) { strs[i] = (String) iter.next(); } return strs; }
/** * Returns a Property representing a bitmask. If <code>rawOutput</code> is true, returns a LIST * property whose elements are STRING properties. The string values of these STRING properties are * the elements of <code>labels</code> whose indices correspond to 1 bits in the bitmask, counting * the low-order bit as bit 0. if <code>rawOutput</code> is false, returns a LONG property whose * numeric value is <code>value</code>. */ protected Property addBitmaskProperty( String name, long value, String[] labels, boolean rawOutput) { Property prop = null; if (!rawOutput) { List list = new LinkedList(); try { for (int i = 0; i < labels.length; i++) { if ((value & (1 << i)) != 0) { list.add(labels[i]); } } } catch (Exception e) { _errors.add(name + " value out of range: " + value); } prop = new Property(name, PropertyType.STRING, PropertyArity.LIST, list); } if (prop == null) { prop = new Property(name, PropertyType.LONG, new Long(value)); } return prop; }
/** * Returns an Property representing an integer value. If <code>rawOutput</code> is true, returns * an INTEGER property, and <code>labels</code> and <code>index</code> are unused. Otherwise, * returns a STRING property, with the string being the element of <code>labels</code> whose index * is <code>value</code>. */ protected Property addIntegerProperty( String name, int value, String[] labels, boolean rawOutput) { Property prop = null; if (!rawOutput) { try { prop = new Property(name, PropertyType.STRING, labels[value]); } catch (Exception e) { _errors.add(name + " value out of range: " + value); } } if (prop == null) { prop = new Property(name, PropertyType.INTEGER, new Integer(value)); } return prop; }
/** * Returns an ARRAY Property representing an integer array. If <code>rawOutput</code> is true, the * elements of the property array are INTEGER properties, and <code>labels</code> is unused. * Otherwise, the elements of the array are STRING properties, with the elements of <code>value * </code> used as indices into <code>labels</code>. */ protected Property addIntegerArrayProperty( String name, int[] value, String[] labels, boolean rawOutput) { Property prop = null; if (!rawOutput) { String[] s = new String[value.length]; for (int i = 0; i < value.length; i++) { try { s[i] = labels[value[i]]; } catch (Exception e) { _errors.add(name + " value out of range: " + value[i]); } } prop = new Property(name, PropertyType.STRING, PropertyArity.ARRAY, s); } if (prop == null) { prop = new Property(name, PropertyType.INTEGER, PropertyArity.ARRAY, value); } return prop; }
/** 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; }
/** * Returns an Property representing an integer value. If <code>rawOutput</code> is true, returns * an INTEGER property, and <code>labels</code> and <code>index</code> are unused. Otherwise, * returns a STRING property, with the string being the element of <code>labels</code> whose index * is the index of <code>value</code> in <code>index</code>. */ protected Property addIntegerProperty( String name, int value, String[] labels, int[] index, boolean rawOutput) { Property prop = null; if (!rawOutput) { int n = -1; for (int i = 0; i < index.length; i++) { if (value == index[i]) { n = i; break; } } if (n > -1) { prop = new Property(name, PropertyType.STRING, labels[n]); } else { _errors.add(name + " value out of range: " + value); } } if (prop == null) { prop = new Property(name, PropertyType.INTEGER, new Integer(value)); } return prop; }
/** * 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); } }
/** * 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; }
/** 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; }