From 19901744fb8cd6eb227ab4529f33021c3d9585cd Mon Sep 17 00:00:00 2001 From: damencho Date: Fri, 13 Dec 2024 12:06:00 -0600 Subject: [PATCH 1/8] feat: Handles case where we overflow void elements. Handles more tags and tracks. --- src/main/java/org/ebml/io/FileDataSource.java | 15 +++- src/main/java/org/ebml/io/FileDataWriter.java | 32 ++++++++ .../org/ebml/matroska/MatroskaFileTags.java | 24 ++++-- .../org/ebml/matroska/MatroskaFileTracks.java | 31 +++++-- .../org/ebml/matroska/MatroskaFileWriter.java | 81 +++++++++++++++++-- .../matroska/VoidOutOfBoundException.java | 5 ++ .../ebml/matroska/MatroskaFileWriterTest.java | 59 ++++++++++++++ 7 files changed, 227 insertions(+), 20 deletions(-) create mode 100644 src/main/java/org/ebml/matroska/VoidOutOfBoundException.java diff --git a/src/main/java/org/ebml/io/FileDataSource.java b/src/main/java/org/ebml/io/FileDataSource.java index f9d5dc8..1598471 100644 --- a/src/main/java/org/ebml/io/FileDataSource.java +++ b/src/main/java/org/ebml/io/FileDataSource.java @@ -2,17 +2,17 @@ * JEBML - Java library to read/write EBML/Matroska elements. * Copyright (C) 2004 Jory Stone * Based on Javatroska (C) 2002 John Cannon - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * 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 fc = file.getChannel(); } + /** + * Converts a writer to a reader. + */ + public FileDataSource(FileDataWriter writer) throws FileNotFoundException, IOException + { + file = writer.file; + fc = writer.fc; + } + @Override public byte readByte() { diff --git a/src/main/java/org/ebml/io/FileDataWriter.java b/src/main/java/org/ebml/io/FileDataWriter.java index e286a46..243218e 100644 --- a/src/main/java/org/ebml/io/FileDataWriter.java +++ b/src/main/java/org/ebml/io/FileDataWriter.java @@ -25,22 +25,32 @@ import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; public class FileDataWriter implements DataWriter, Closeable { RandomAccessFile file = null; FileChannel fc = null; + /** + * We need this in order to be able to replace the file with another one. + */ + String filename = null; + public FileDataWriter(final String filename) throws FileNotFoundException, IOException { file = new RandomAccessFile(filename, "rw"); fc = file.getChannel(); + this.filename = filename; } public FileDataWriter(final String filename, final String mode) throws FileNotFoundException, IOException { file = new RandomAccessFile(filename, mode); fc = file.getChannel(); + this.filename = filename; } @Override @@ -121,4 +131,26 @@ public void close() throws IOException { file.close(); } + + /** + * Copy data from source to source position into current file starting from the beginning. + */ + public void copyToPosition(FileDataWriter src) throws IOException + { + src.fc.transferTo(0, src.fc.position(), fc); + } + + /** + * Copy from current position of src to its end to the current file on current position. + */ + public void copyFromPosition(FileDataWriter src) throws IOException + { + src.fc.transferTo(src.fc.position(), src.fc.size() - src.fc.position(), fc); + } + + public void replaceWithFile(FileDataWriter dw) + throws IOException + { + Files.move(Path.of(dw.filename), Path.of(this.filename), StandardCopyOption.REPLACE_EXISTING); + } } diff --git a/src/main/java/org/ebml/matroska/MatroskaFileTags.java b/src/main/java/org/ebml/matroska/MatroskaFileTags.java index 4d8c3ad..aaa6b16 100644 --- a/src/main/java/org/ebml/matroska/MatroskaFileTags.java +++ b/src/main/java/org/ebml/matroska/MatroskaFileTags.java @@ -21,7 +21,8 @@ public void addTag(final MatroskaFileTagEntry tag) tags.add(tag); } - public long writeTags(final DataWriter ioDW) + public long writeTags(final DataWriter ioDW, boolean checkBlockSize) + throws VoidOutOfBoundException { myPosition = ioDW.getFilePointer(); final MasterElement tagsElem = MatroskaDocTypes.Tags.getInstance(); @@ -30,21 +31,34 @@ public long writeTags(final DataWriter ioDW) { tagsElem.addChildElement(tag.toElement()); } + + if (checkBlockSize && BLOCK_SIZE < tagsElem.getTotalSize()) + { + LOG.warn("Tags element size exceeds block size!"); + + throw new VoidOutOfBoundException(); + } + long len = tagsElem.writeElement(ioDW); - if (ioDW.isSeekable()) + + if (BLOCK_SIZE > tagsElem.getTotalSize()) { new VoidElement(BLOCK_SIZE - tagsElem.getTotalSize()).writeElement(ioDW); return BLOCK_SIZE; } - return len; + else + { + return len; + } } - public long update(final DataWriter ioDW) + public long update(final DataWriter ioDW, boolean checkBlockSize) + throws VoidOutOfBoundException { LOG.info("Updating tags list!"); final long start = ioDW.getFilePointer(); ioDW.seek(myPosition); - long len = writeTags(ioDW); + long len = writeTags(ioDW, checkBlockSize); ioDW.seek(start); return len; } diff --git a/src/main/java/org/ebml/matroska/MatroskaFileTracks.java b/src/main/java/org/ebml/matroska/MatroskaFileTracks.java index bb19db2..61abd63 100644 --- a/src/main/java/org/ebml/matroska/MatroskaFileTracks.java +++ b/src/main/java/org/ebml/matroska/MatroskaFileTracks.java @@ -21,7 +21,8 @@ public void addTrack(final MatroskaFileTrack track) tracks.add(track); } - public long writeTracks(final DataWriter ioDW) + public long writeTracks(final DataWriter ioDW, boolean checkBlockSize) + throws VoidOutOfBoundException { myPosition = ioDW.getFilePointer(); final MasterElement tracksElem = MatroskaDocTypes.Tracks.getInstance(); @@ -30,18 +31,34 @@ public long writeTracks(final DataWriter ioDW) { tracksElem.addChildElement(track.toElement()); } - tracksElem.writeElement(ioDW); - assert BLOCK_SIZE > tracksElem.getTotalSize(); - new VoidElement(BLOCK_SIZE - tracksElem.getTotalSize()).writeElement(ioDW); - return BLOCK_SIZE; + + if (checkBlockSize && BLOCK_SIZE < tracksElem.getTotalSize()) + { + LOG.warn("Tracks element size exceeds block size!"); + + throw new VoidOutOfBoundException(); + } + + long size = tracksElem.writeElement(ioDW); + + if (BLOCK_SIZE > tracksElem.getTotalSize()) + { + new VoidElement(BLOCK_SIZE - tracksElem.getTotalSize()).writeElement(ioDW); + return BLOCK_SIZE; + } + else + { + return size; + } } - public long update(final DataWriter ioDW) + public long update(final DataWriter ioDW, boolean checkBlockSize) + throws VoidOutOfBoundException { LOG.info("Updating tracks list!"); final long start = ioDW.getFilePointer(); ioDW.seek(myPosition); - long len = writeTracks(ioDW); + long len = writeTracks(ioDW, checkBlockSize); ioDW.seek(start); return len; } diff --git a/src/main/java/org/ebml/matroska/MatroskaFileWriter.java b/src/main/java/org/ebml/matroska/MatroskaFileWriter.java index 80c8a20..4b82627 100644 --- a/src/main/java/org/ebml/matroska/MatroskaFileWriter.java +++ b/src/main/java/org/ebml/matroska/MatroskaFileWriter.java @@ -20,14 +20,17 @@ package org.ebml.matroska; import java.io.Closeable; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.HashSet; import java.util.Set; -import java.util.concurrent.*; import org.ebml.MasterElement; import org.ebml.StringElement; import org.ebml.UnsignedIntegerElement; import org.ebml.io.DataWriter; +import org.ebml.io.FileDataWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -88,10 +91,26 @@ void initialize() segmentInfoElem.writeElement(ioDW); metaSeek.addIndexedElement(MatroskaDocTypes.Tracks.getType(), ioDW.getFilePointer()); - tracks.writeTracks(ioDW); + try + { + tracks.writeTracks(ioDW, false); + } + catch (VoidOutOfBoundException e) + { + // when initializing tracks are empty so this should never happen + LOG.error("Tracks element size exceeds block size?", e); + } metaSeek.addIndexedElement(MatroskaDocTypes.Tags.getType(), ioDW.getFilePointer()); - tags.writeTags(ioDW); + try + { + tags.writeTags(ioDW, false); + } + catch (VoidOutOfBoundException e) + { + // when initializing tags are empty so this should never happen + LOG.error("Tags element size exceeds block size?", e); + } cluster = new MatroskaCluster(); metaSeek.addIndexedElement(MatroskaDocTypes.Cluster.getType(), ioDW.getFilePointer()); @@ -283,8 +302,41 @@ public void close() segmentInfoElem.setDuration(maxSegmentTimecode - minSegmentTimecode); segmentLen += segmentInfoElem.update(ioDW); - segmentLen += tracks.update(ioDW); - segmentLen += tags.update(ioDW); + try + { + segmentLen += tracks.update(ioDW, true); + } + catch (VoidOutOfBoundException e) + { + LOG.info("Tracks element size exceeds block size. Will expand file."); + + try (FileDataWriter dw = copyBeginningOfFile()) + { + segmentLen += tracks.update(dw, false); + copyEndOfFileAndClose(dw); + } + catch (VoidOutOfBoundException | IOException ex) + { + throw new RuntimeException(ex); + } + } + try + { + segmentLen += tags.update(ioDW, true); + } + catch (VoidOutOfBoundException e) + { + LOG.info("Tags element size exceeds block size. Will expand file."); + try (FileDataWriter dw = copyBeginningOfFile()) + { + segmentLen += tags.update(dw, false); + copyEndOfFileAndClose(dw); + } + catch (VoidOutOfBoundException | IOException ex) + { + throw new RuntimeException(ex); + } + } segmentLen += clusterLen; segmentElem.setUnknownSize(false); @@ -292,4 +344,23 @@ public void close() segmentElem.update(ioDW); } } + + private FileDataWriter copyBeginningOfFile() + throws IOException + { + FileDataWriter dw = new FileDataWriter(Files.createTempFile("mka", ".tmp").toFile().getPath()); + dw.copyToPosition((FileDataWriter)ioDW); + + return dw; + } + + private void copyEndOfFileAndClose(FileDataWriter dw) + throws IOException + { + dw.copyFromPosition((FileDataWriter)ioDW); + + ((FileDataWriter) ioDW).close(); + ((FileDataWriter) ioDW).replaceWithFile(dw); + + } } diff --git a/src/main/java/org/ebml/matroska/VoidOutOfBoundException.java b/src/main/java/org/ebml/matroska/VoidOutOfBoundException.java new file mode 100644 index 0000000..9042355 --- /dev/null +++ b/src/main/java/org/ebml/matroska/VoidOutOfBoundException.java @@ -0,0 +1,5 @@ +package org.ebml.matroska; + +public class VoidOutOfBoundException extends Exception +{ +} diff --git a/src/test/java/org/ebml/matroska/MatroskaFileWriterTest.java b/src/test/java/org/ebml/matroska/MatroskaFileWriterTest.java index 2fe1787..966e587 100644 --- a/src/test/java/org/ebml/matroska/MatroskaFileWriterTest.java +++ b/src/test/java/org/ebml/matroska/MatroskaFileWriterTest.java @@ -117,6 +117,65 @@ public void testMultipleTracks() throws Exception testDocTraversal(); } + @Test + public void testMultipleTracksToFillVoidArea() throws Exception + { + final MatroskaFileWriter writer = new MatroskaFileWriter(ioDW); + + for (int i = 0; i < 40; i++) + { + final MatroskaFileTrack nextTrack = new MatroskaFileTrack(); + nextTrack.setTrackNo(i + 2); + nextTrack.setTrackType(TrackType.CONTROL); + nextTrack.setCodecID("some codec"); + nextTrack.setDefaultDuration(4242); + writer.addTrack(nextTrack); + } + + writer.addFrame(generateFrame("I know a song...", 42)); + writer.addFrame(generateFrame("that gets on everybody's nerves", 2)); + + writer.close(); + + final FileDataSource inputDataSource = new FileDataSource(destination.getPath()); + final MatroskaFile reader = new MatroskaFile(inputDataSource); + reader.readFile(); + assertEquals(40, reader.getTrackList().length); + LOG.info(reader.getReport()); + testDocTraversal(); + } + + @Test + public void testMultipleTags() throws Exception + { + final MatroskaFileWriter writer = new MatroskaFileWriter(ioDW); + writer.addTrack(testTrack); + + for (int i = 0; i < 100; i++) + { + final MatroskaFileTagEntry tag = new MatroskaFileTagEntry(); + final MatroskaFileSimpleTag simpleTag = new MatroskaFileSimpleTag(); + simpleTag.setName("MyTITLE" + i); + simpleTag.setValue("MyCanon in D" + i); + tag.addSimpleTag(simpleTag); + writer.addTag(tag); + } + + writer.addFrame(generateFrame("I know a song...", 42)); + writer.addFrame(generateFrame("that gets on everybody's nerves", 2)); + + writer.close(); + + final FileDataSource inputDataSource = new FileDataSource(destination.getPath()); + final MatroskaFile reader = new MatroskaFile(inputDataSource); + reader.readFile(); + assertEquals(TrackType.SUBTITLE, reader.getTrackList()[0].getTrackType()); + assertEquals(42, reader.getTrackList()[0].getTrackNo()); + assertEquals(100, reader.getTagList().size()); + LOG.info(reader.getReport()); + testDocTraversal(); + } + @Test public void testSilentTrack() throws FileNotFoundException, IOException { From a6c475f0011abc3ead9d6e857fcb02d048f48197 Mon Sep 17 00:00:00 2001 From: damencho Date: Fri, 13 Dec 2024 13:02:13 -0600 Subject: [PATCH 2/8] squash: Account for isSeekable when creating void block. --- src/main/java/org/ebml/matroska/MatroskaFileTags.java | 8 +++----- src/main/java/org/ebml/matroska/MatroskaFileTracks.java | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/ebml/matroska/MatroskaFileTags.java b/src/main/java/org/ebml/matroska/MatroskaFileTags.java index aaa6b16..e8b498b 100644 --- a/src/main/java/org/ebml/matroska/MatroskaFileTags.java +++ b/src/main/java/org/ebml/matroska/MatroskaFileTags.java @@ -41,15 +41,13 @@ public long writeTags(final DataWriter ioDW, boolean checkBlockSize) long len = tagsElem.writeElement(ioDW); - if (BLOCK_SIZE > tagsElem.getTotalSize()) + if (BLOCK_SIZE > tagsElem.getTotalSize() && ioDW.isSeekable()) { new VoidElement(BLOCK_SIZE - tagsElem.getTotalSize()).writeElement(ioDW); return BLOCK_SIZE; } - else - { - return len; - } + + return len; } public long update(final DataWriter ioDW, boolean checkBlockSize) diff --git a/src/main/java/org/ebml/matroska/MatroskaFileTracks.java b/src/main/java/org/ebml/matroska/MatroskaFileTracks.java index 61abd63..3baf473 100644 --- a/src/main/java/org/ebml/matroska/MatroskaFileTracks.java +++ b/src/main/java/org/ebml/matroska/MatroskaFileTracks.java @@ -41,15 +41,13 @@ public long writeTracks(final DataWriter ioDW, boolean checkBlockSize) long size = tracksElem.writeElement(ioDW); - if (BLOCK_SIZE > tracksElem.getTotalSize()) + if (BLOCK_SIZE > tracksElem.getTotalSize() && ioDW.isSeekable()) { new VoidElement(BLOCK_SIZE - tracksElem.getTotalSize()).writeElement(ioDW); return BLOCK_SIZE; } - else - { - return size; - } + + return size; } public long update(final DataWriter ioDW, boolean checkBlockSize) From a2fc515fcc9ff23b584c5f7dfe59ebe4d9c50ed7 Mon Sep 17 00:00:00 2001 From: damencho Date: Tue, 17 Dec 2024 11:50:31 -0600 Subject: [PATCH 3/8] squash: Adds logs for exception we swallow. --- src/main/java/org/ebml/io/FileDataWriter.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/org/ebml/io/FileDataWriter.java b/src/main/java/org/ebml/io/FileDataWriter.java index 243218e..3843e50 100644 --- a/src/main/java/org/ebml/io/FileDataWriter.java +++ b/src/main/java/org/ebml/io/FileDataWriter.java @@ -19,6 +19,9 @@ */ package org.ebml.io; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.Closeable; import java.io.FileNotFoundException; import java.io.IOException; @@ -31,6 +34,8 @@ public class FileDataWriter implements DataWriter, Closeable { + private static final Logger LOG = LoggerFactory.getLogger(FileDataWriter.class); + RandomAccessFile file = null; FileChannel fc = null; @@ -63,6 +68,7 @@ public int write(final byte b) } catch (final IOException ex) { + LOG.error("Failed to write byte", ex); return 0; } } @@ -76,6 +82,7 @@ public int write(final ByteBuffer buff) } catch (final IOException ex) { + LOG.error("Failed to write buffer", ex); return 0; } } @@ -89,6 +96,7 @@ public long length() } catch (final IOException ex) { + LOG.error("Failed to get length", ex); return -1; } } @@ -102,6 +110,7 @@ public long getFilePointer() } catch (final IOException ex) { + LOG.error("Failed to get pointer", ex); return -1; } } @@ -122,6 +131,7 @@ public long seek(final long pos) } catch (final IOException ex) { + LOG.error("Failed to seek", ex); return -1; } } From 083f49cc70cc8b3d6260dee01884be8096b785c7 Mon Sep 17 00:00:00 2001 From: damencho Date: Tue, 17 Dec 2024 11:50:55 -0600 Subject: [PATCH 4/8] squash: Recreates the file after the move, open it again. --- src/main/java/org/ebml/io/FileDataWriter.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/ebml/io/FileDataWriter.java b/src/main/java/org/ebml/io/FileDataWriter.java index 3843e50..482e4fd 100644 --- a/src/main/java/org/ebml/io/FileDataWriter.java +++ b/src/main/java/org/ebml/io/FileDataWriter.java @@ -162,5 +162,9 @@ public void replaceWithFile(FileDataWriter dw) throws IOException { Files.move(Path.of(dw.filename), Path.of(this.filename), StandardCopyOption.REPLACE_EXISTING); + + // recreate after we move the new file + file = new RandomAccessFile(filename, "rw"); + fc = file.getChannel(); } } From 38f491c81d3ea6f33dbdc44c62762ca5614dce02 Mon Sep 17 00:00:00 2001 From: damencho Date: Tue, 17 Dec 2024 14:26:38 -0600 Subject: [PATCH 5/8] squash: Moves the logic into tracks and tag and fix it by seeking the correct src position when coping the end of the file. --- src/main/java/org/ebml/io/FileDataWriter.java | 38 +++++++-- .../org/ebml/matroska/MatroskaFileTags.java | 46 ++++++++-- .../org/ebml/matroska/MatroskaFileTracks.java | 51 ++++++++++-- .../org/ebml/matroska/MatroskaFileWriter.java | 83 ++----------------- .../matroska/VoidOutOfBoundException.java | 5 -- 5 files changed, 119 insertions(+), 104 deletions(-) delete mode 100644 src/main/java/org/ebml/matroska/VoidOutOfBoundException.java diff --git a/src/main/java/org/ebml/io/FileDataWriter.java b/src/main/java/org/ebml/io/FileDataWriter.java index 482e4fd..7df1b45 100644 --- a/src/main/java/org/ebml/io/FileDataWriter.java +++ b/src/main/java/org/ebml/io/FileDataWriter.java @@ -158,13 +158,39 @@ public void copyFromPosition(FileDataWriter src) throws IOException src.fc.transferTo(src.fc.position(), src.fc.size() - src.fc.position(), fc); } - public void replaceWithFile(FileDataWriter dw) - throws IOException + /** + * Copies the beginning of the file to a temporary file. + * @return the FileDataWriter for the temporary file. + * @throws IOException + */ + public FileDataWriter copyBeginningOfFile() + throws IOException { - Files.move(Path.of(dw.filename), Path.of(this.filename), StandardCopyOption.REPLACE_EXISTING); + Path f = Files.createTempFile("mka", ".tmp"); + + FileDataWriter dw = new FileDataWriter(f.toFile().getPath()); + dw.copyToPosition(this); + + return dw; + } + + /** + * Copies the end of the current file(from current position) into the supplied FileDataWriter and replaces + * the current file with the temp one. + * @param dw The FileDataWriter to use as a new file. + * @throws IOException + */ + public void copyEndOfFile(FileDataWriter dw) + throws IOException + { + dw.copyFromPosition(this); + + this.close(); + + Files.move(Path.of(dw.filename), Path.of(this.filename), StandardCopyOption.REPLACE_EXISTING); - // recreate after we move the new file - file = new RandomAccessFile(filename, "rw"); - fc = file.getChannel(); + // recreate after we move the new file + file = new RandomAccessFile(filename, "rw"); + fc = file.getChannel(); } } diff --git a/src/main/java/org/ebml/matroska/MatroskaFileTags.java b/src/main/java/org/ebml/matroska/MatroskaFileTags.java index e8b498b..c45354c 100644 --- a/src/main/java/org/ebml/matroska/MatroskaFileTags.java +++ b/src/main/java/org/ebml/matroska/MatroskaFileTags.java @@ -1,13 +1,18 @@ package org.ebml.matroska; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.IOException; import java.util.ArrayList; import org.ebml.MasterElement; import org.ebml.io.DataWriter; +import org.ebml.io.FileDataWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MatroskaFileTags + implements PropertyChangeListener { private static final int BLOCK_SIZE = 4096; private static final Logger LOG = LoggerFactory.getLogger(MatroskaFileTags.class); @@ -21,8 +26,7 @@ public void addTag(final MatroskaFileTagEntry tag) tags.add(tag); } - public long writeTags(final DataWriter ioDW, boolean checkBlockSize) - throws VoidOutOfBoundException + public long writeTags(final DataWriter ioDW) { myPosition = ioDW.getFilePointer(); final MasterElement tagsElem = MatroskaDocTypes.Tags.getInstance(); @@ -32,11 +36,29 @@ public long writeTags(final DataWriter ioDW, boolean checkBlockSize) tagsElem.addChildElement(tag.toElement()); } - if (checkBlockSize && BLOCK_SIZE < tagsElem.getTotalSize()) + if (BLOCK_SIZE < tagsElem.getTotalSize() && ioDW.isSeekable()) { - LOG.warn("Tags element size exceeds block size!"); + long len; - throw new VoidOutOfBoundException(); + // we need to write beyond the void space we have reserved + // copy beginning of file into a temporary file + try (FileDataWriter dw = ((FileDataWriter)ioDW).copyBeginningOfFile()) + { + // write the tags + len = tagsElem.writeElement(dw); + + // now let's copy the rest of the original file by first setting the position after the tags + ioDW.seek(myPosition + BLOCK_SIZE); + + // copy the rest of the original file + ((FileDataWriter)ioDW).copyEndOfFile(dw); + } + catch (IOException ex) + { + throw new RuntimeException(ex); + } + + return len; } long len = tagsElem.writeElement(ioDW); @@ -50,14 +72,22 @@ public long writeTags(final DataWriter ioDW, boolean checkBlockSize) return len; } - public long update(final DataWriter ioDW, boolean checkBlockSize) - throws VoidOutOfBoundException + public long update(final DataWriter ioDW) { LOG.info("Updating tags list!"); final long start = ioDW.getFilePointer(); ioDW.seek(myPosition); - long len = writeTags(ioDW, checkBlockSize); + long len = writeTags(ioDW); ioDW.seek(start); return len; } + + @Override + public void propertyChange(PropertyChangeEvent evt) + { + if (evt.getPropertyName().equals(MatroskaFileTracks.RESIZED)) + { + myPosition = myPosition + ((long) evt.getNewValue() - (long) evt.getOldValue()); + } + } } diff --git a/src/main/java/org/ebml/matroska/MatroskaFileTracks.java b/src/main/java/org/ebml/matroska/MatroskaFileTracks.java index 3baf473..3c98af1 100644 --- a/src/main/java/org/ebml/matroska/MatroskaFileTracks.java +++ b/src/main/java/org/ebml/matroska/MatroskaFileTracks.java @@ -1,15 +1,23 @@ package org.ebml.matroska; +import java.beans.PropertyChangeSupport; +import java.beans.PropertyChangeListener; +import java.io.IOException; import java.util.ArrayList; import org.ebml.MasterElement; import org.ebml.io.DataWriter; +import org.ebml.io.FileDataWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MatroskaFileTracks { - private static final int BLOCK_SIZE = 4096; + final public static String RESIZED = "resized"; + + private final PropertyChangeSupport listeners = new PropertyChangeSupport(this); + + private static final long BLOCK_SIZE = 4096; private static final Logger LOG = LoggerFactory.getLogger(MatroskaFileTracks.class); private final ArrayList tracks = new ArrayList<>(); @@ -21,8 +29,7 @@ public void addTrack(final MatroskaFileTrack track) tracks.add(track); } - public long writeTracks(final DataWriter ioDW, boolean checkBlockSize) - throws VoidOutOfBoundException + public long writeTracks(final DataWriter ioDW) { myPosition = ioDW.getFilePointer(); final MasterElement tracksElem = MatroskaDocTypes.Tracks.getInstance(); @@ -32,11 +39,33 @@ public long writeTracks(final DataWriter ioDW, boolean checkBlockSize) tracksElem.addChildElement(track.toElement()); } - if (checkBlockSize && BLOCK_SIZE < tracksElem.getTotalSize()) + if (BLOCK_SIZE < tracksElem.getTotalSize() && ioDW.isSeekable()) { - LOG.warn("Tracks element size exceeds block size!"); + long len; + + // we need to write beyond the void space we have reserved + // copy beginning of file into a temporary file + try (FileDataWriter dw = ((FileDataWriter)ioDW).copyBeginningOfFile()) + { + // write the tracks + len = tracksElem.writeElement(dw); + + // now let's copy the rest of the original file by first setting the position after the tracks + ioDW.seek(myPosition + BLOCK_SIZE); + + // copy the rest of the original file + ((FileDataWriter)ioDW).copyEndOfFile(dw); + } + catch (IOException ex) + { + throw new RuntimeException(ex); + } - throw new VoidOutOfBoundException(); + // we need to update tags element that its current position changed as we moved the data and inserted + // some data before tags + this.listeners.firePropertyChange(RESIZED, BLOCK_SIZE, len); + + return len; } long size = tracksElem.writeElement(ioDW); @@ -50,14 +79,18 @@ public long writeTracks(final DataWriter ioDW, boolean checkBlockSize) return size; } - public long update(final DataWriter ioDW, boolean checkBlockSize) - throws VoidOutOfBoundException + public long update(final DataWriter ioDW) { LOG.info("Updating tracks list!"); final long start = ioDW.getFilePointer(); ioDW.seek(myPosition); - long len = writeTracks(ioDW, checkBlockSize); + long len = writeTracks(ioDW); ioDW.seek(start); return len; } + + public void addPropertyChangeListener(PropertyChangeListener listener) + { + this.listeners.addPropertyChangeListener(listener); + } } diff --git a/src/main/java/org/ebml/matroska/MatroskaFileWriter.java b/src/main/java/org/ebml/matroska/MatroskaFileWriter.java index 4b82627..f8413a9 100644 --- a/src/main/java/org/ebml/matroska/MatroskaFileWriter.java +++ b/src/main/java/org/ebml/matroska/MatroskaFileWriter.java @@ -20,9 +20,6 @@ package org.ebml.matroska; import java.io.Closeable; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.HashSet; import java.util.Set; @@ -30,7 +27,6 @@ import org.ebml.StringElement; import org.ebml.UnsignedIntegerElement; import org.ebml.io.DataWriter; -import org.ebml.io.FileDataWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -91,26 +87,13 @@ void initialize() segmentInfoElem.writeElement(ioDW); metaSeek.addIndexedElement(MatroskaDocTypes.Tracks.getType(), ioDW.getFilePointer()); - try - { - tracks.writeTracks(ioDW, false); - } - catch (VoidOutOfBoundException e) - { - // when initializing tracks are empty so this should never happen - LOG.error("Tracks element size exceeds block size?", e); - } + tracks.writeTracks(ioDW); metaSeek.addIndexedElement(MatroskaDocTypes.Tags.getType(), ioDW.getFilePointer()); - try - { - tags.writeTags(ioDW, false); - } - catch (VoidOutOfBoundException e) - { - // when initializing tags are empty so this should never happen - LOG.error("Tags element size exceeds block size?", e); - } + tags.writeTags(ioDW); + + // If tracks got expanded beyond the void element, tags needs to adjust its pointer + tracks.addPropertyChangeListener(tags); cluster = new MatroskaCluster(); metaSeek.addIndexedElement(MatroskaDocTypes.Cluster.getType(), ioDW.getFilePointer()); @@ -302,41 +285,8 @@ public void close() segmentInfoElem.setDuration(maxSegmentTimecode - minSegmentTimecode); segmentLen += segmentInfoElem.update(ioDW); - try - { - segmentLen += tracks.update(ioDW, true); - } - catch (VoidOutOfBoundException e) - { - LOG.info("Tracks element size exceeds block size. Will expand file."); - - try (FileDataWriter dw = copyBeginningOfFile()) - { - segmentLen += tracks.update(dw, false); - copyEndOfFileAndClose(dw); - } - catch (VoidOutOfBoundException | IOException ex) - { - throw new RuntimeException(ex); - } - } - try - { - segmentLen += tags.update(ioDW, true); - } - catch (VoidOutOfBoundException e) - { - LOG.info("Tags element size exceeds block size. Will expand file."); - try (FileDataWriter dw = copyBeginningOfFile()) - { - segmentLen += tags.update(dw, false); - copyEndOfFileAndClose(dw); - } - catch (VoidOutOfBoundException | IOException ex) - { - throw new RuntimeException(ex); - } - } + segmentLen += tracks.update(ioDW); + segmentLen += tags.update(ioDW); segmentLen += clusterLen; segmentElem.setUnknownSize(false); @@ -344,23 +294,4 @@ public void close() segmentElem.update(ioDW); } } - - private FileDataWriter copyBeginningOfFile() - throws IOException - { - FileDataWriter dw = new FileDataWriter(Files.createTempFile("mka", ".tmp").toFile().getPath()); - dw.copyToPosition((FileDataWriter)ioDW); - - return dw; - } - - private void copyEndOfFileAndClose(FileDataWriter dw) - throws IOException - { - dw.copyFromPosition((FileDataWriter)ioDW); - - ((FileDataWriter) ioDW).close(); - ((FileDataWriter) ioDW).replaceWithFile(dw); - - } } diff --git a/src/main/java/org/ebml/matroska/VoidOutOfBoundException.java b/src/main/java/org/ebml/matroska/VoidOutOfBoundException.java deleted file mode 100644 index 9042355..0000000 --- a/src/main/java/org/ebml/matroska/VoidOutOfBoundException.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.ebml.matroska; - -public class VoidOutOfBoundException extends Exception -{ -} From fb87ddcd4979404ca0b6ef9ae806986721a2915f Mon Sep 17 00:00:00 2001 From: damencho Date: Tue, 17 Dec 2024 18:36:51 -0600 Subject: [PATCH 6/8] squash: Fix seeking the original file. --- .../org/ebml/matroska/MatroskaFileTags.java | 23 +++++++++++++------ .../org/ebml/matroska/MatroskaFileTracks.java | 19 +++++++++++---- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/ebml/matroska/MatroskaFileTags.java b/src/main/java/org/ebml/matroska/MatroskaFileTags.java index c45354c..d420343 100644 --- a/src/main/java/org/ebml/matroska/MatroskaFileTags.java +++ b/src/main/java/org/ebml/matroska/MatroskaFileTags.java @@ -19,7 +19,8 @@ public class MatroskaFileTags private final ArrayList tags = new ArrayList<>(); - private long myPosition; + private long myStartPosition; + private long myEndPosition; public void addTag(final MatroskaFileTagEntry tag) { @@ -28,7 +29,7 @@ public void addTag(final MatroskaFileTagEntry tag) public long writeTags(final DataWriter ioDW) { - myPosition = ioDW.getFilePointer(); + myStartPosition = ioDW.getFilePointer(); final MasterElement tagsElem = MatroskaDocTypes.Tags.getInstance(); for (final MatroskaFileTagEntry tag : tags) @@ -36,7 +37,10 @@ public long writeTags(final DataWriter ioDW) tagsElem.addChildElement(tag.toElement()); } - if (BLOCK_SIZE < tagsElem.getTotalSize() && ioDW.isSeekable()) + if (BLOCK_SIZE < tagsElem.getTotalSize() && ioDW.isSeekable() + // do the shuffling the data only if the file is big enough to contain the data + // if it is not it means we are writing the file for the first time and we don't need to shuffle the data + && ioDW.length() > myStartPosition + tagsElem.getTotalSize()) { long len; @@ -48,10 +52,13 @@ public long writeTags(final DataWriter ioDW) len = tagsElem.writeElement(dw); // now let's copy the rest of the original file by first setting the position after the tags - ioDW.seek(myPosition + BLOCK_SIZE); + ioDW.seek(myEndPosition); // copy the rest of the original file ((FileDataWriter)ioDW).copyEndOfFile(dw); + myEndPosition = myStartPosition + len; + + ioDW.seek(myEndPosition); } catch (IOException ex) { @@ -62,7 +69,7 @@ public long writeTags(final DataWriter ioDW) } long len = tagsElem.writeElement(ioDW); - + myEndPosition = ioDW.getFilePointer(); if (BLOCK_SIZE > tagsElem.getTotalSize() && ioDW.isSeekable()) { new VoidElement(BLOCK_SIZE - tagsElem.getTotalSize()).writeElement(ioDW); @@ -76,7 +83,7 @@ public long update(final DataWriter ioDW) { LOG.info("Updating tags list!"); final long start = ioDW.getFilePointer(); - ioDW.seek(myPosition); + ioDW.seek(myStartPosition); long len = writeTags(ioDW); ioDW.seek(start); return len; @@ -87,7 +94,9 @@ public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(MatroskaFileTracks.RESIZED)) { - myPosition = myPosition + ((long) evt.getNewValue() - (long) evt.getOldValue()); + long increase = (long) evt.getNewValue() - (long) evt.getOldValue(); + myStartPosition += increase; + myEndPosition += increase; } } } diff --git a/src/main/java/org/ebml/matroska/MatroskaFileTracks.java b/src/main/java/org/ebml/matroska/MatroskaFileTracks.java index 3c98af1..7c739e0 100644 --- a/src/main/java/org/ebml/matroska/MatroskaFileTracks.java +++ b/src/main/java/org/ebml/matroska/MatroskaFileTracks.java @@ -22,7 +22,8 @@ public class MatroskaFileTracks private final ArrayList tracks = new ArrayList<>(); - private long myPosition; + private long myStartPosition; + private long myEndPosition; public void addTrack(final MatroskaFileTrack track) { @@ -31,7 +32,7 @@ public void addTrack(final MatroskaFileTrack track) public long writeTracks(final DataWriter ioDW) { - myPosition = ioDW.getFilePointer(); + myStartPosition = ioDW.getFilePointer(); final MasterElement tracksElem = MatroskaDocTypes.Tracks.getInstance(); for (final MatroskaFileTrack track : tracks) @@ -39,7 +40,10 @@ public long writeTracks(final DataWriter ioDW) tracksElem.addChildElement(track.toElement()); } - if (BLOCK_SIZE < tracksElem.getTotalSize() && ioDW.isSeekable()) + if (BLOCK_SIZE < tracksElem.getTotalSize() && ioDW.isSeekable() + // do the shuffling the data only if the file is big enough to contain the data + // if it is not it means we are writing the file for the first time and we don't need to shuffle the data + && ioDW.length() > myStartPosition + tracksElem.getTotalSize()) { long len; @@ -51,10 +55,14 @@ public long writeTracks(final DataWriter ioDW) len = tracksElem.writeElement(dw); // now let's copy the rest of the original file by first setting the position after the tracks - ioDW.seek(myPosition + BLOCK_SIZE); + ioDW.seek(myEndPosition); // copy the rest of the original file ((FileDataWriter)ioDW).copyEndOfFile(dw); + + myEndPosition = myStartPosition + len; + + ioDW.seek(myEndPosition); } catch (IOException ex) { @@ -70,6 +78,7 @@ public long writeTracks(final DataWriter ioDW) long size = tracksElem.writeElement(ioDW); + myEndPosition = ioDW.getFilePointer(); if (BLOCK_SIZE > tracksElem.getTotalSize() && ioDW.isSeekable()) { new VoidElement(BLOCK_SIZE - tracksElem.getTotalSize()).writeElement(ioDW); @@ -83,7 +92,7 @@ public long update(final DataWriter ioDW) { LOG.info("Updating tracks list!"); final long start = ioDW.getFilePointer(); - ioDW.seek(myPosition); + ioDW.seek(myStartPosition); long len = writeTracks(ioDW); ioDW.seek(start); return len; From 61697324ddab85e2e968bec663f592c756f4487e Mon Sep 17 00:00:00 2001 From: damencho Date: Tue, 17 Dec 2024 19:02:39 -0600 Subject: [PATCH 7/8] squash: Fix setting end position. It was skipping the void data. --- src/main/java/org/ebml/matroska/MatroskaFileTags.java | 7 ++++--- src/main/java/org/ebml/matroska/MatroskaFileTracks.java | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/ebml/matroska/MatroskaFileTags.java b/src/main/java/org/ebml/matroska/MatroskaFileTags.java index d420343..97cc250 100644 --- a/src/main/java/org/ebml/matroska/MatroskaFileTags.java +++ b/src/main/java/org/ebml/matroska/MatroskaFileTags.java @@ -46,16 +46,16 @@ public long writeTags(final DataWriter ioDW) // we need to write beyond the void space we have reserved // copy beginning of file into a temporary file - try (FileDataWriter dw = ((FileDataWriter)ioDW).copyBeginningOfFile()) + try (FileDataWriter tmp = ((FileDataWriter)ioDW).copyBeginningOfFile()) { // write the tags - len = tagsElem.writeElement(dw); + len = tagsElem.writeElement(tmp); // now let's copy the rest of the original file by first setting the position after the tags ioDW.seek(myEndPosition); // copy the rest of the original file - ((FileDataWriter)ioDW).copyEndOfFile(dw); + ((FileDataWriter)ioDW).copyEndOfFile(tmp); myEndPosition = myStartPosition + len; ioDW.seek(myEndPosition); @@ -73,6 +73,7 @@ public long writeTags(final DataWriter ioDW) if (BLOCK_SIZE > tagsElem.getTotalSize() && ioDW.isSeekable()) { new VoidElement(BLOCK_SIZE - tagsElem.getTotalSize()).writeElement(ioDW); + myEndPosition = ioDW.getFilePointer(); return BLOCK_SIZE; } diff --git a/src/main/java/org/ebml/matroska/MatroskaFileTracks.java b/src/main/java/org/ebml/matroska/MatroskaFileTracks.java index 7c739e0..8c3169f 100644 --- a/src/main/java/org/ebml/matroska/MatroskaFileTracks.java +++ b/src/main/java/org/ebml/matroska/MatroskaFileTracks.java @@ -49,16 +49,16 @@ public long writeTracks(final DataWriter ioDW) // we need to write beyond the void space we have reserved // copy beginning of file into a temporary file - try (FileDataWriter dw = ((FileDataWriter)ioDW).copyBeginningOfFile()) + try (FileDataWriter tmp = ((FileDataWriter)ioDW).copyBeginningOfFile()) { // write the tracks - len = tracksElem.writeElement(dw); + len = tracksElem.writeElement(tmp); // now let's copy the rest of the original file by first setting the position after the tracks ioDW.seek(myEndPosition); // copy the rest of the original file - ((FileDataWriter)ioDW).copyEndOfFile(dw); + ((FileDataWriter)ioDW).copyEndOfFile(tmp); myEndPosition = myStartPosition + len; @@ -82,6 +82,7 @@ public long writeTracks(final DataWriter ioDW) if (BLOCK_SIZE > tracksElem.getTotalSize() && ioDW.isSeekable()) { new VoidElement(BLOCK_SIZE - tracksElem.getTotalSize()).writeElement(ioDW); + myEndPosition = ioDW.getFilePointer(); return BLOCK_SIZE; } From 406bbaaad8b74477dda181ffaa2b2757458737a5 Mon Sep 17 00:00:00 2001 From: damencho Date: Wed, 18 Dec 2024 12:41:17 -0600 Subject: [PATCH 8/8] squash: Fix comment. --- src/test/java/org/ebml/matroska/MatroskaFileWriterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/ebml/matroska/MatroskaFileWriterTest.java b/src/test/java/org/ebml/matroska/MatroskaFileWriterTest.java index 966e587..68c28a4 100644 --- a/src/test/java/org/ebml/matroska/MatroskaFileWriterTest.java +++ b/src/test/java/org/ebml/matroska/MatroskaFileWriterTest.java @@ -125,7 +125,7 @@ public void testMultipleTracksToFillVoidArea() throws Exception for (int i = 0; i < 40; i++) { final MatroskaFileTrack nextTrack = new MatroskaFileTrack(); - nextTrack.setTrackNo(i + 2); + nextTrack.setTrackNo(i); nextTrack.setTrackType(TrackType.CONTROL); nextTrack.setCodecID("some codec"); nextTrack.setDefaultDuration(4242);