Refactor Mp4FaststartPostProcessor.

This commit is contained in:
Nicholas Tinsley 2024-01-17 20:04:08 -05:00 committed by Greyson Parrelli
parent 8e7383be05
commit 7bb1c58452
4 changed files with 30 additions and 29 deletions

View file

@ -42,10 +42,8 @@ import org.thoughtcrime.securesms.video.InMemoryTranscoder;
import org.thoughtcrime.securesms.video.StreamingTranscoder;
import org.thoughtcrime.securesms.video.TranscoderCancelationSignal;
import org.thoughtcrime.securesms.video.TranscoderOptions;
import org.thoughtcrime.securesms.video.exceptions.VideoPostProcessingException;
import org.thoughtcrime.securesms.video.exceptions.VideoSourceException;
import org.thoughtcrime.securesms.video.postprocessing.Mp4FaststartPostProcessor;
import org.thoughtcrime.securesms.video.exceptions.VideoSourceException;
import org.thoughtcrime.securesms.video.videoconverter.EncodingException;
import java.io.ByteArrayInputStream;
@ -263,7 +261,7 @@ public final class AttachmentCompressionJob extends BaseJob {
Log.i(TAG, "Compressing with streaming muxer");
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
File file = SignalDatabase.attachments().newFile(context);
File file = AttachmentTable.newFile(context);
file.deleteOnExit();
boolean faststart = false;
@ -291,9 +289,10 @@ public final class AttachmentCompressionJob extends BaseJob {
} catch (IOException e) {
throw new RuntimeException(e);
}
}, file.length());
});
try (MediaStream mediaStream = new MediaStream(postProcessor.process(), MimeTypes.VIDEO_MP4, 0, 0, true)) {
final long plaintextLength = ModernEncryptingPartOutputStream.getPlaintextLength(file.length());
try (MediaStream mediaStream = new MediaStream(postProcessor.process(plaintextLength), MimeTypes.VIDEO_MP4, 0, 0, true)) {
attachmentDatabase.updateAttachmentData(attachment, mediaStream, true);
faststart = true;
}

View file

@ -179,9 +179,9 @@ public final class InMemoryTranscoder implements Closeable {
Log.w(TAG, "IOException thrown while creating FileInputStream.", e);
throw new VideoPostProcessingException("Exception while opening InputStream!", e);
}
}, memoryFile.size());
});
return new MediaStream(postProcessor.process(), MimeTypes.VIDEO_MP4, 0, 0, true);
return new MediaStream(postProcessor.process(outSize), MimeTypes.VIDEO_MP4, 0, 0, true);
} catch (VideoPostProcessingException e) {
Log.w(TAG, "Exception thrown during post processing.", e);
final Throwable cause = e.getCause();

View file

@ -113,10 +113,7 @@ class TranscodeWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(
return Result.failure()
}
tempFileLength = tempFileStream.readLength() ?: run {
Log.w(TAG, "$logPrefix Could not read file length of temp file descriptor!")
return Result.failure()
}
tempFileLength = tempFileStream.readLength()
}
val finalFile = createFile(Uri.parse(outputDirUri), finalFilename) ?: run {
Log.w(TAG, "$logPrefix Could not create final file for faststart processing!")
@ -129,7 +126,7 @@ class TranscodeWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(
}
val inputStreamFactory = { applicationContext.contentResolver.openInputStream(tempFile.uri) ?: throw IOException("Could not open temp file for reading!") }
val bytesCopied = Mp4FaststartPostProcessor(inputStreamFactory, tempFileLength).processAndWriteTo(finalFileStream)
val bytesCopied = Mp4FaststartPostProcessor(inputStreamFactory).processAndWriteTo(finalFileStream)
if (bytesCopied != tempFileLength) {
Log.w(TAG, "$logPrefix Postprocessing failed! Original transcoded filesize ($tempFileLength) did not match postprocessed filesize ($bytesCopied)")

View file

@ -5,9 +5,9 @@
package org.thoughtcrime.securesms.video.postprocessing
import org.signal.core.util.readLength
import org.signal.libsignal.media.Mp4Sanitizer
import org.signal.libsignal.media.SanitizedMetadata
import org.thoughtcrime.securesms.video.exceptions.VideoPostProcessingException
import java.io.ByteArrayInputStream
import java.io.FilterInputStream
import java.io.IOException
@ -18,31 +18,22 @@ import java.io.SequenceInputStream
/**
* A post processor that takes a stream of bytes, and using [Mp4Sanitizer], moves the metadata to the front of the file.
*
* @property inputStreamFactory factory for the [InputStream]. May be called multiple times.
* @property inputLength the exact length of the [InputStream]
* @property inputStreamFactory factory for the [InputStream]. Expected to be called multiple times.
*/
class Mp4FaststartPostProcessor(private val inputStreamFactory: InputStreamFactory, private val inputLength: Long) {
class Mp4FaststartPostProcessor(private val inputStreamFactory: InputStreamFactory) {
/**
* It is the responsibility of the called to close the resulting [InputStream]
* It is the responsibility of the caller to close the resulting [InputStream].
*/
fun process(): InputStream {
val metadata: SanitizedMetadata?
inputStreamFactory.create().use {
metadata = Mp4Sanitizer.sanitize(it, inputLength)
}
if (metadata?.sanitizedMetadata == null) {
throw VideoPostProcessingException("Mp4Sanitizer could not parse media metadata!")
}
fun process(inputLength: Long = calculateStreamLength(inputStreamFactory.create())): SequenceInputStream {
val metadata = sanitizeMetadata(inputStreamFactory.create(), inputLength)
val inputStream = inputStreamFactory.create()
inputStream.skip(metadata.dataOffset)
return SequenceInputStream(ByteArrayInputStream(metadata.sanitizedMetadata), LimitedInputStream(inputStream, metadata.dataLength))
}
fun processAndWriteTo(outputStream: OutputStream): Long {
process().use { inStream ->
fun processAndWriteTo(outputStream: OutputStream, inputLength: Long = calculateStreamLength(inputStreamFactory.create())): Long {
process(inputLength).use { inStream ->
return inStream.copyTo(outputStream)
}
}
@ -53,6 +44,20 @@ class Mp4FaststartPostProcessor(private val inputStreamFactory: InputStreamFacto
companion object {
const val TAG = "Mp4Faststart"
@JvmStatic
fun calculateStreamLength(inputStream: InputStream): Long {
inputStream.use {
return it.readLength()
}
}
@JvmStatic
private fun sanitizeMetadata(inputStream: InputStream, inputLength: Long): SanitizedMetadata {
inputStream.use {
return Mp4Sanitizer.sanitize(it, inputLength)
}
}
}
private class LimitedInputStream(innerStream: InputStream, limit: Long) : FilterInputStream(innerStream) {