Skip to content

Commit 1990174

Browse files
committed
feat: Handles case where we overflow void elements.
Handles more tags and tracks.
1 parent 09e9281 commit 1990174

7 files changed

+227
-20
lines changed

src/main/java/org/ebml/io/FileDataSource.java

+12-3
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22
* JEBML - Java library to read/write EBML/Matroska elements.
33
* Copyright (C) 2004 Jory Stone <[email protected]>
44
* Based on Javatroska (C) 2002 John Cannon <[email protected]>
5-
*
5+
*
66
* This library is free software; you can redistribute it and/or
77
* modify it under the terms of the GNU Lesser General Public
88
* License as published by the Free Software Foundation; either
99
* version 2.1 of the License, or (at your option) any later version.
10-
*
10+
*
1111
* This library is distributed in the hope that it will be useful,
1212
* but WITHOUT ANY WARRANTY; without even the implied warranty of
1313
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1414
* Lesser General Public License for more details.
15-
*
15+
*
1616
* You should have received a copy of the GNU Lesser General Public
1717
* License along with this library; if not, write to the Free Software
1818
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
@@ -42,6 +42,15 @@ public FileDataSource(final String filename, final String mode) throws FileNotFo
4242
fc = file.getChannel();
4343
}
4444

45+
/**
46+
* Converts a writer to a reader.
47+
*/
48+
public FileDataSource(FileDataWriter writer) throws FileNotFoundException, IOException
49+
{
50+
file = writer.file;
51+
fc = writer.fc;
52+
}
53+
4554
@Override
4655
public byte readByte()
4756
{

src/main/java/org/ebml/io/FileDataWriter.java

+32
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,32 @@
2525
import java.io.RandomAccessFile;
2626
import java.nio.ByteBuffer;
2727
import java.nio.channels.FileChannel;
28+
import java.nio.file.Files;
29+
import java.nio.file.Path;
30+
import java.nio.file.StandardCopyOption;
2831

2932
public class FileDataWriter implements DataWriter, Closeable
3033
{
3134
RandomAccessFile file = null;
3235
FileChannel fc = null;
3336

37+
/**
38+
* We need this in order to be able to replace the file with another one.
39+
*/
40+
String filename = null;
41+
3442
public FileDataWriter(final String filename) throws FileNotFoundException, IOException
3543
{
3644
file = new RandomAccessFile(filename, "rw");
3745
fc = file.getChannel();
46+
this.filename = filename;
3847
}
3948

4049
public FileDataWriter(final String filename, final String mode) throws FileNotFoundException, IOException
4150
{
4251
file = new RandomAccessFile(filename, mode);
4352
fc = file.getChannel();
53+
this.filename = filename;
4454
}
4555

4656
@Override
@@ -121,4 +131,26 @@ public void close() throws IOException
121131
{
122132
file.close();
123133
}
134+
135+
/**
136+
* Copy data from source to source position into current file starting from the beginning.
137+
*/
138+
public void copyToPosition(FileDataWriter src) throws IOException
139+
{
140+
src.fc.transferTo(0, src.fc.position(), fc);
141+
}
142+
143+
/**
144+
* Copy from current position of src to its end to the current file on current position.
145+
*/
146+
public void copyFromPosition(FileDataWriter src) throws IOException
147+
{
148+
src.fc.transferTo(src.fc.position(), src.fc.size() - src.fc.position(), fc);
149+
}
150+
151+
public void replaceWithFile(FileDataWriter dw)
152+
throws IOException
153+
{
154+
Files.move(Path.of(dw.filename), Path.of(this.filename), StandardCopyOption.REPLACE_EXISTING);
155+
}
124156
}

src/main/java/org/ebml/matroska/MatroskaFileTags.java

+19-5
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ public void addTag(final MatroskaFileTagEntry tag)
2121
tags.add(tag);
2222
}
2323

24-
public long writeTags(final DataWriter ioDW)
24+
public long writeTags(final DataWriter ioDW, boolean checkBlockSize)
25+
throws VoidOutOfBoundException
2526
{
2627
myPosition = ioDW.getFilePointer();
2728
final MasterElement tagsElem = MatroskaDocTypes.Tags.getInstance();
@@ -30,21 +31,34 @@ public long writeTags(final DataWriter ioDW)
3031
{
3132
tagsElem.addChildElement(tag.toElement());
3233
}
34+
35+
if (checkBlockSize && BLOCK_SIZE < tagsElem.getTotalSize())
36+
{
37+
LOG.warn("Tags element size exceeds block size!");
38+
39+
throw new VoidOutOfBoundException();
40+
}
41+
3342
long len = tagsElem.writeElement(ioDW);
34-
if (ioDW.isSeekable())
43+
44+
if (BLOCK_SIZE > tagsElem.getTotalSize())
3545
{
3646
new VoidElement(BLOCK_SIZE - tagsElem.getTotalSize()).writeElement(ioDW);
3747
return BLOCK_SIZE;
3848
}
39-
return len;
49+
else
50+
{
51+
return len;
52+
}
4053
}
4154

42-
public long update(final DataWriter ioDW)
55+
public long update(final DataWriter ioDW, boolean checkBlockSize)
56+
throws VoidOutOfBoundException
4357
{
4458
LOG.info("Updating tags list!");
4559
final long start = ioDW.getFilePointer();
4660
ioDW.seek(myPosition);
47-
long len = writeTags(ioDW);
61+
long len = writeTags(ioDW, checkBlockSize);
4862
ioDW.seek(start);
4963
return len;
5064
}

src/main/java/org/ebml/matroska/MatroskaFileTracks.java

+24-7
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ public void addTrack(final MatroskaFileTrack track)
2121
tracks.add(track);
2222
}
2323

24-
public long writeTracks(final DataWriter ioDW)
24+
public long writeTracks(final DataWriter ioDW, boolean checkBlockSize)
25+
throws VoidOutOfBoundException
2526
{
2627
myPosition = ioDW.getFilePointer();
2728
final MasterElement tracksElem = MatroskaDocTypes.Tracks.getInstance();
@@ -30,18 +31,34 @@ public long writeTracks(final DataWriter ioDW)
3031
{
3132
tracksElem.addChildElement(track.toElement());
3233
}
33-
tracksElem.writeElement(ioDW);
34-
assert BLOCK_SIZE > tracksElem.getTotalSize();
35-
new VoidElement(BLOCK_SIZE - tracksElem.getTotalSize()).writeElement(ioDW);
36-
return BLOCK_SIZE;
34+
35+
if (checkBlockSize && BLOCK_SIZE < tracksElem.getTotalSize())
36+
{
37+
LOG.warn("Tracks element size exceeds block size!");
38+
39+
throw new VoidOutOfBoundException();
40+
}
41+
42+
long size = tracksElem.writeElement(ioDW);
43+
44+
if (BLOCK_SIZE > tracksElem.getTotalSize())
45+
{
46+
new VoidElement(BLOCK_SIZE - tracksElem.getTotalSize()).writeElement(ioDW);
47+
return BLOCK_SIZE;
48+
}
49+
else
50+
{
51+
return size;
52+
}
3753
}
3854

39-
public long update(final DataWriter ioDW)
55+
public long update(final DataWriter ioDW, boolean checkBlockSize)
56+
throws VoidOutOfBoundException
4057
{
4158
LOG.info("Updating tracks list!");
4259
final long start = ioDW.getFilePointer();
4360
ioDW.seek(myPosition);
44-
long len = writeTracks(ioDW);
61+
long len = writeTracks(ioDW, checkBlockSize);
4562
ioDW.seek(start);
4663
return len;
4764
}

src/main/java/org/ebml/matroska/MatroskaFileWriter.java

+76-5
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,17 @@
2020
package org.ebml.matroska;
2121

2222
import java.io.Closeable;
23+
import java.io.IOException;
24+
import java.nio.file.Files;
25+
import java.nio.file.Path;
2326
import java.util.HashSet;
2427
import java.util.Set;
25-
import java.util.concurrent.*;
2628

2729
import org.ebml.MasterElement;
2830
import org.ebml.StringElement;
2931
import org.ebml.UnsignedIntegerElement;
3032
import org.ebml.io.DataWriter;
33+
import org.ebml.io.FileDataWriter;
3134
import org.slf4j.Logger;
3235
import org.slf4j.LoggerFactory;
3336

@@ -88,10 +91,26 @@ void initialize()
8891
segmentInfoElem.writeElement(ioDW);
8992

9093
metaSeek.addIndexedElement(MatroskaDocTypes.Tracks.getType(), ioDW.getFilePointer());
91-
tracks.writeTracks(ioDW);
94+
try
95+
{
96+
tracks.writeTracks(ioDW, false);
97+
}
98+
catch (VoidOutOfBoundException e)
99+
{
100+
// when initializing tracks are empty so this should never happen
101+
LOG.error("Tracks element size exceeds block size?", e);
102+
}
92103

93104
metaSeek.addIndexedElement(MatroskaDocTypes.Tags.getType(), ioDW.getFilePointer());
94-
tags.writeTags(ioDW);
105+
try
106+
{
107+
tags.writeTags(ioDW, false);
108+
}
109+
catch (VoidOutOfBoundException e)
110+
{
111+
// when initializing tags are empty so this should never happen
112+
LOG.error("Tags element size exceeds block size?", e);
113+
}
95114

96115
cluster = new MatroskaCluster();
97116
metaSeek.addIndexedElement(MatroskaDocTypes.Cluster.getType(), ioDW.getFilePointer());
@@ -283,13 +302,65 @@ public void close()
283302
segmentInfoElem.setDuration(maxSegmentTimecode - minSegmentTimecode);
284303
segmentLen += segmentInfoElem.update(ioDW);
285304

286-
segmentLen += tracks.update(ioDW);
287-
segmentLen += tags.update(ioDW);
305+
try
306+
{
307+
segmentLen += tracks.update(ioDW, true);
308+
}
309+
catch (VoidOutOfBoundException e)
310+
{
311+
LOG.info("Tracks element size exceeds block size. Will expand file.");
312+
313+
try (FileDataWriter dw = copyBeginningOfFile())
314+
{
315+
segmentLen += tracks.update(dw, false);
316+
copyEndOfFileAndClose(dw);
317+
}
318+
catch (VoidOutOfBoundException | IOException ex)
319+
{
320+
throw new RuntimeException(ex);
321+
}
322+
}
323+
try
324+
{
325+
segmentLen += tags.update(ioDW, true);
326+
}
327+
catch (VoidOutOfBoundException e)
328+
{
329+
LOG.info("Tags element size exceeds block size. Will expand file.");
330+
try (FileDataWriter dw = copyBeginningOfFile())
331+
{
332+
segmentLen += tags.update(dw, false);
333+
copyEndOfFileAndClose(dw);
334+
}
335+
catch (VoidOutOfBoundException | IOException ex)
336+
{
337+
throw new RuntimeException(ex);
338+
}
339+
}
288340
segmentLen += clusterLen;
289341

290342
segmentElem.setUnknownSize(false);
291343
segmentElem.setSize(segmentLen);
292344
segmentElem.update(ioDW);
293345
}
294346
}
347+
348+
private FileDataWriter copyBeginningOfFile()
349+
throws IOException
350+
{
351+
FileDataWriter dw = new FileDataWriter(Files.createTempFile("mka", ".tmp").toFile().getPath());
352+
dw.copyToPosition((FileDataWriter)ioDW);
353+
354+
return dw;
355+
}
356+
357+
private void copyEndOfFileAndClose(FileDataWriter dw)
358+
throws IOException
359+
{
360+
dw.copyFromPosition((FileDataWriter)ioDW);
361+
362+
((FileDataWriter) ioDW).close();
363+
((FileDataWriter) ioDW).replaceWithFile(dw);
364+
365+
}
295366
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package org.ebml.matroska;
2+
3+
public class VoidOutOfBoundException extends Exception
4+
{
5+
}

src/test/java/org/ebml/matroska/MatroskaFileWriterTest.java

+59
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,65 @@ public void testMultipleTracks() throws Exception
117117
testDocTraversal();
118118
}
119119

120+
@Test
121+
public void testMultipleTracksToFillVoidArea() throws Exception
122+
{
123+
final MatroskaFileWriter writer = new MatroskaFileWriter(ioDW);
124+
125+
for (int i = 0; i < 40; i++)
126+
{
127+
final MatroskaFileTrack nextTrack = new MatroskaFileTrack();
128+
nextTrack.setTrackNo(i + 2);
129+
nextTrack.setTrackType(TrackType.CONTROL);
130+
nextTrack.setCodecID("some codec");
131+
nextTrack.setDefaultDuration(4242);
132+
writer.addTrack(nextTrack);
133+
}
134+
135+
writer.addFrame(generateFrame("I know a song...", 42));
136+
writer.addFrame(generateFrame("that gets on everybody's nerves", 2));
137+
138+
writer.close();
139+
140+
final FileDataSource inputDataSource = new FileDataSource(destination.getPath());
141+
final MatroskaFile reader = new MatroskaFile(inputDataSource);
142+
reader.readFile();
143+
assertEquals(40, reader.getTrackList().length);
144+
LOG.info(reader.getReport());
145+
testDocTraversal();
146+
}
147+
148+
@Test
149+
public void testMultipleTags() throws Exception
150+
{
151+
final MatroskaFileWriter writer = new MatroskaFileWriter(ioDW);
152+
writer.addTrack(testTrack);
153+
154+
for (int i = 0; i < 100; i++)
155+
{
156+
final MatroskaFileTagEntry tag = new MatroskaFileTagEntry();
157+
final MatroskaFileSimpleTag simpleTag = new MatroskaFileSimpleTag();
158+
simpleTag.setName("MyTITLE" + i);
159+
simpleTag.setValue("MyCanon in D" + i);
160+
tag.addSimpleTag(simpleTag);
161+
writer.addTag(tag);
162+
}
163+
164+
writer.addFrame(generateFrame("I know a song...", 42));
165+
writer.addFrame(generateFrame("that gets on everybody's nerves", 2));
166+
167+
writer.close();
168+
169+
final FileDataSource inputDataSource = new FileDataSource(destination.getPath());
170+
final MatroskaFile reader = new MatroskaFile(inputDataSource);
171+
reader.readFile();
172+
assertEquals(TrackType.SUBTITLE, reader.getTrackList()[0].getTrackType());
173+
assertEquals(42, reader.getTrackList()[0].getTrackNo());
174+
assertEquals(100, reader.getTagList().size());
175+
LOG.info(reader.getReport());
176+
testDocTraversal();
177+
}
178+
120179
@Test
121180
public void testSilentTrack() throws FileNotFoundException, IOException
122181
{

0 commit comments

Comments
 (0)