diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java index a1bc49945..45038f7a2 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java @@ -70,9 +70,10 @@ public class RecordTypes { public static final Type List = new Type(2000,null); public static final Type FontCollection = new Type(2005,FontCollection.class); public static final Type BookmarkCollection = new Type(2019,null); + public static final Type SoundCollection = new Type(2020,SoundCollection.class); public static final Type SoundCollAtom = new Type(2021,null); - public static final Type Sound = new Type(2022,null); - public static final Type SoundData = new Type(2023,null); + public static final Type Sound = new Type(2022,Sound.class); + public static final Type SoundData = new Type(2023,SoundData.class); public static final Type BookmarkSeedAtom = new Type(2025,null); public static final Type ColorSchemeAtom = new Type(2032,ColorSchemeAtom.class); public static final Type ExObjRefAtom = new Type(3009,null); diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/Sound.java b/src/scratchpad/src/org/apache/poi/hslf/record/Sound.java new file mode 100755 index 000000000..211a9b0ee --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/Sound.java @@ -0,0 +1,136 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.hslf.record; + +import org.apache.poi.util.POILogger; + +import java.io.OutputStream; +import java.io.IOException; + +/** + * A container holding information about a sound. It contains: + *

+ *

  • 1. CString (4026), Instance 0: Name of sound (e.g. "crash") + *
  • 2. CString (4026), Instance 1: Type of sound (e.g. ".wav") + *
  • 3. CString (4026), Instance 2: Reference id of sound in sound collection + *
  • 4. CString (4026), Instance 3, optional: Built-in id of sound, for sounds we ship. This is the id that?s in the reg file. + *
  • 5. SoundData (2023), optional + *

    + * + * @author Yegor Kozlov + */ +public class Sound extends RecordContainer { + /** + * Record header data. + */ + private byte[] _header; + + // Links to our more interesting children + private CString _name; + private CString _type; + private SoundData _data; + + + /** + * Set things up, and find our more interesting children + * + * @param source the source data as a byte array. + * @param start the start offset into the byte array. + * @param len the length of the slice in the byte array. + */ + protected Sound(byte[] source, int start, int len) { + // Grab the header + _header = new byte[8]; + System.arraycopy(source,start,_header,0,8); + + // Find our children + _children = Record.findChildRecords(source,start+8,len-8); + findInterestingChildren(); + } + + private void findInterestingChildren() { + // First child should be the ExHyperlinkAtom + if(_children[0] instanceof CString) { + _name = (CString)_children[0]; + } else { + logger.log(POILogger.ERROR, "First child record wasn't a CString, was of type " + _children[0].getRecordType()); + } + + // Second child should be the ExOleObjAtom + if (_children[1] instanceof CString) { + _type = (CString)_children[1]; + } else { + logger.log(POILogger.ERROR, "Second child record wasn't a CString, was of type " + _children[1].getRecordType()); + } + + for (int i = 2; i < _children.length; i++) { + if(_children[i] instanceof SoundData){ + _data = (SoundData)_children[i]; + break; + } + } + + } + + /** + * Returns the type (held as a little endian in bytes 3 and 4) + * that this class handles. + * + * @return the record type. + */ + public long getRecordType() { + return RecordTypes.Sound.typeID; + } + + /** + * Have the contents printer out into an OutputStream, used when + * writing a file back out to disk. + * + * @param out the output stream. + * @throws java.io.IOException if there was an error writing to the stream. + */ + public void writeOut(OutputStream out) throws IOException { + writeOut(_header[0],_header[1],getRecordType(),_children,out); + } + + /** + * Name of the sound (e.g. "crash") + * + * @return name of the sound + */ + public String getSoundName(){ + return _name.getText(); + } + + /** + * Type of the sound (e.g. ".wav") + * + * @return type of the sound + */ + public String getSoundType(){ + return _type.getText(); + } + + /** + * The sound data + * + * @return the sound data. + */ + public byte[] getSoundData(){ + return _data == null ? null : _data.getData(); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/SoundCollection.java b/src/scratchpad/src/org/apache/poi/hslf/record/SoundCollection.java new file mode 100755 index 000000000..c244859ea --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/SoundCollection.java @@ -0,0 +1,73 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.hslf.record; + +import org.apache.poi.util.POILogger; + +import java.io.OutputStream; +import java.io.IOException; + +/** + * Is a container for all sound related atoms and containers. It contains: + *
  • 1. SoundCollAtom (2021) + *
  • 2. Sound (2022), for each sound, if any + * + * @author Yegor Kozlov + */ +public class SoundCollection extends RecordContainer { + /** + * Record header data. + */ + private byte[] _header; + + /** + * Set things up, and find our more interesting children + * + * @param source the source data as a byte array. + * @param start the start offset into the byte array. + * @param len the length of the slice in the byte array. + */ + protected SoundCollection(byte[] source, int start, int len) { + // Grab the header + _header = new byte[8]; + System.arraycopy(source,start,_header,0,8); + + // Find our children + _children = Record.findChildRecords(source,start+8,len-8); + } + + /** + * Returns the type (held as a little endian in bytes 3 and 4) + * that this class handles. + * + * @return the record type. + */ + public long getRecordType() { + return RecordTypes.SoundCollection.typeID; + } + + /** + * Have the contents printer out into an OutputStream, used when + * writing a file back out to disk. + * + * @param out the output stream. + * @throws java.io.IOException if there was an error writing to the stream. + */ + public void writeOut(OutputStream out) throws IOException { + writeOut(_header[0],_header[1],getRecordType(),_children,out); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/SoundData.java b/src/scratchpad/src/org/apache/poi/hslf/record/SoundData.java new file mode 100755 index 000000000..42be48f2a --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/SoundData.java @@ -0,0 +1,103 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hslf.record; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.InflaterInputStream; + +import org.apache.poi.util.LittleEndian; + +/** + * Storage for embedded sounds. + * + * @author Yegor Kozlov + */ +public class SoundData extends RecordAtom { + + /** + * Record header. + */ + private byte[] _header; + + /** + * Record data. + */ + private byte[] _data; + + /** + * Constructs a new empty sound container. + */ + protected SoundData() { + _header = new byte[8]; + _data = new byte[0]; + + LittleEndian.putShort(_header, 2, (short)getRecordType()); + LittleEndian.putInt(_header, 4, _data.length); + } + + /** + * Constructs the link related atom record from its + * source data. + * + * @param source the source data as a byte array. + * @param start the start offset into the byte array. + * @param len the length of the slice in the byte array. + */ + protected SoundData(byte[] source, int start, int len) { + // Get the header. + _header = new byte[8]; + System.arraycopy(source,start,_header,0,8); + + // Get the record data. + _data = new byte[len-8]; + System.arraycopy(source,start+8,_data,0,len-8); + } + + /** + * Returns the sound data. + * + * @return the sound data + */ + public byte[] getData() { + return _data; + } + + /** + * Gets the record type. + * + * @return the record type. + */ + public long getRecordType() { + return RecordTypes.SoundData.typeID; + } + + /** + * Write the contents of the record back, so it can be written + * to disk. + * + * @param out the output stream to write to. + * @throws java.io.IOException if an error occurs. + */ + public void writeOut(OutputStream out) throws IOException { + out.write(_header); + out.write(_data); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java index 418dc3d2f..828255087 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java @@ -494,6 +494,14 @@ public class SlideShow public ObjectData[] getEmbeddedObjects() { return _hslfSlideShow.getEmbeddedObjects(); } + + /** + * Returns the data of all the embedded sounds in the SlideShow + */ + public SoundData[] getSoundData() { + return SoundData.find(_documentRecord); + } + /** * Return the current page size */ diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SoundData.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SoundData.java new file mode 100755 index 000000000..ad7922ef6 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SoundData.java @@ -0,0 +1,93 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.hslf.usermodel; + +import org.apache.poi.hslf.record.*; + +import java.util.ArrayList; + +/** + * A class that represents sound data embedded in a slide show. + * + * @author Yegor Kozlov + */ +public class SoundData { + /** + * The record that contains the object data. + */ + private Sound _container; + + /** + * Creates the object data wrapping the record that contains the sound data. + * + * @param container the record that contains the sound data. + */ + public SoundData(Sound container) { + this._container = container; + } + + /** + * Name of the sound (e.g. "crash") + * + * @return name of the sound + */ + public String getSoundName(){ + return _container.getSoundName(); + } + + /** + * Type of the sound (e.g. ".wav") + * + * @return type of the sound + */ + public String getSoundType(){ + return _container.getSoundType(); + } + + /** + * Gets an input stream which returns the binary of the sound data. + * + * @return the input stream which will contain the binary of the sound data. + */ + public byte[] getData() { + return _container.getSoundData(); + } + + /** + * Find all sound records in the supplied Document records + * + * @param document the document to find in + * @return the array with the sound data + */ + public static SoundData[] find(Document document){ + ArrayList lst = new ArrayList(); + Record[] ch = document.getChildRecords(); + for (int i = 0; i < ch.length; i++) { + if(ch[i].getRecordType() == RecordTypes.SoundCollection.typeID){ + RecordContainer col = (RecordContainer)ch[i]; + Record[] sr = col.getChildRecords(); + for (int j = 0; j < sr.length; j++) { + if(sr[j] instanceof Sound){ + lst.add(new SoundData((Sound)sr[j])); + } + } + } + + } + return (SoundData[])lst.toArray(new SoundData[lst.size()]); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/data/ringin.wav b/src/scratchpad/testcases/org/apache/poi/hslf/data/ringin.wav new file mode 100755 index 000000000..f8d4292d0 Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hslf/data/ringin.wav differ diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/data/sound.ppt b/src/scratchpad/testcases/org/apache/poi/hslf/data/sound.ppt new file mode 100755 index 000000000..2d1316acf Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hslf/data/sound.ppt differ diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestSound.java b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestSound.java new file mode 100755 index 000000000..97a4b7c5b --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestSound.java @@ -0,0 +1,86 @@ + +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + + + +package org.apache.poi.hslf.record; + + +import junit.framework.TestCase; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Arrays; + +import org.apache.poi.hslf.HSLFSlideShow; +import org.apache.poi.hslf.usermodel.SlideShow; + +/** + * Tests Sound-related records: SoundCollection(2020), Sound(2022) and SoundData(2023)). + * + * @author Yegor Kozlov + */ +public class TestSound extends TestCase { + public void testRealFile() throws Exception { + String cwd = System.getProperty("HSLF.testdata.path"); + FileInputStream is = new FileInputStream(new File(cwd, "sound.ppt")); + SlideShow ppt = new SlideShow(is); + is.close(); + + // Get the document + Document doc = ppt.getDocumentRecord(); + SoundCollection soundCollection = null; + Record[] doc_ch = doc.getChildRecords(); + for (int i = 0; i < doc_ch.length; i++) { + if(doc_ch[i] instanceof SoundCollection){ + soundCollection = (SoundCollection)doc_ch[i]; + break; + } + } + assertNotNull(soundCollection); + + Sound sound = null; + Record[] sound_ch = soundCollection.getChildRecords(); + int k = 0; + for (int i = 0; i < sound_ch.length; i++) { + if(sound_ch[i] instanceof Sound){ + sound = (Sound)sound_ch[i]; + k++; + } + } + assertNotNull(sound); + assertEquals(1, k); + + assertEquals("ringin.wav", sound.getSoundName()); + assertEquals(".WAV", sound.getSoundType()); + assertNotNull(sound.getSoundData()); + + File f = new File(cwd, "ringin.wav"); + int length = (int)f.length(); + byte[] ref_data = new byte[length]; + is = new FileInputStream(f); + is.read(ref_data); + is.close(); + + assertTrue(Arrays.equals(ref_data, sound.getSoundData())); + + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestSoundData.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestSoundData.java new file mode 100755 index 000000000..95de38f8b --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestSoundData.java @@ -0,0 +1,63 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hslf.usermodel; + +import org.apache.poi.hslf.*; +import org.apache.poi.hslf.exceptions.HSLFException; +import org.apache.poi.hslf.blip.*; +import org.apache.poi.hslf.model.*; +import junit.framework.TestCase; + +import java.io.*; +import java.util.Arrays; + +/** + * Test reading sound data from a ppt + * + * @author Yegor Kozlov + */ +public class TestSoundData extends TestCase{ + + protected File cwd; + + public void setUp() throws Exception { + cwd = new File(System.getProperty("HSLF.testdata.path")); + } + + /** + * Read a reference sound file from disk and compare it from the data extracted from the slide show + */ + public void testSounds() throws Exception { + //read the reference sound file + File f = new File(cwd, "ringin.wav"); + int length = (int)f.length(); + byte[] ref_data = new byte[length]; + FileInputStream is = new FileInputStream(f); + is.read(ref_data); + is.close(); + + is = new FileInputStream(new File(cwd, "sound.ppt")); + SlideShow ppt = new SlideShow(is); + is.close(); + + SoundData[] sound = ppt.getSoundData(); + assertEquals("Expected 1 sound", 1, sound.length); + + assertTrue(Arrays.equals(ref_data, sound[0].getData())); + } +}