Only generate incremental mac for faststart videos.

This commit is contained in:
Nicholas 2023-11-28 09:36:32 -05:00 committed by Cody Henthorne
parent 1fd6aae3d9
commit 67ef831681
17 changed files with 96 additions and 49 deletions

View file

@ -942,10 +942,16 @@ public class AttachmentTable extends DatabaseTable {
}
}
public void markAttachmentAsTransformed(@NonNull AttachmentId attachmentId) {
public void markAttachmentAsTransformed(@NonNull AttachmentId attachmentId, boolean withFaststart) {
getWritableDatabase().beginTransaction();
try {
updateAttachmentTransformProperties(attachmentId, getTransformProperties(attachmentId).withSkipTransform());
TransformProperties transformProperties = getTransformProperties(attachmentId).withSkipTransform();
if (withFaststart) {
transformProperties = transformProperties.withMp4Faststart();
}
updateAttachmentTransformProperties(attachmentId, transformProperties);
getWritableDatabase().setTransactionSuccessful();
} catch (Exception e) {
Log.w(TAG, "Could not mark attachment as transformed.", e);
@ -1645,19 +1651,22 @@ public class AttachmentTable extends DatabaseTable {
@JsonProperty private final long videoTrimStartTimeUs;
@JsonProperty private final long videoTrimEndTimeUs;
@JsonProperty private final int sentMediaQuality;
@JsonProperty private final boolean mp4Faststart;
@JsonCreator
public TransformProperties(@JsonProperty("skipTransform") boolean skipTransform,
@JsonProperty("videoTrim") boolean videoTrim,
@JsonProperty("videoTrimStartTimeUs") long videoTrimStartTimeUs,
@JsonProperty("videoTrimEndTimeUs") long videoTrimEndTimeUs,
@JsonProperty("sentMediaQuality") int sentMediaQuality)
@JsonProperty("sentMediaQuality") int sentMediaQuality,
@JsonProperty("mp4Faststart") boolean mp4Faststart)
{
this.skipTransform = skipTransform;
this.videoTrim = videoTrim;
this.videoTrimStartTimeUs = videoTrimStartTimeUs;
this.videoTrimEndTimeUs = videoTrimEndTimeUs;
this.sentMediaQuality = sentMediaQuality;
this.mp4Faststart = mp4Faststart;
}
protected TransformProperties(Parcel in) {
@ -1666,6 +1675,7 @@ public class AttachmentTable extends DatabaseTable {
videoTrimStartTimeUs = in.readLong();
videoTrimEndTimeUs = in.readLong();
sentMediaQuality = in.readInt();
mp4Faststart = in.readByte() != 0;
}
@Override
@ -1675,6 +1685,7 @@ public class AttachmentTable extends DatabaseTable {
dest.writeLong(videoTrimStartTimeUs);
dest.writeLong(videoTrimEndTimeUs);
dest.writeInt(sentMediaQuality);
dest.writeByte((byte) (mp4Faststart ? 1 : 0));
}
@Override
@ -1695,20 +1706,20 @@ public class AttachmentTable extends DatabaseTable {
};
public static @NonNull TransformProperties empty() {
return new TransformProperties(false, false, 0, 0, DEFAULT_MEDIA_QUALITY);
return new TransformProperties(false, false, 0, 0, DEFAULT_MEDIA_QUALITY, false);
}
public static @NonNull TransformProperties forSkipTransform() {
return new TransformProperties(true, false, 0, 0, DEFAULT_MEDIA_QUALITY);
return new TransformProperties(true, false, 0, 0, DEFAULT_MEDIA_QUALITY, false);
}
public static @NonNull TransformProperties forVideoTrim(long videoTrimStartTimeUs, long videoTrimEndTimeUs) {
return new TransformProperties(false, true, videoTrimStartTimeUs, videoTrimEndTimeUs, DEFAULT_MEDIA_QUALITY);
return new TransformProperties(false, true, videoTrimStartTimeUs, videoTrimEndTimeUs, DEFAULT_MEDIA_QUALITY, false);
}
public static @NonNull TransformProperties forSentMediaQuality(@NonNull Optional<TransformProperties> currentProperties, @NonNull SentMediaQuality sentMediaQuality) {
TransformProperties existing = currentProperties.orElse(empty());
return new TransformProperties(existing.skipTransform, existing.videoTrim, existing.videoTrimStartTimeUs, existing.videoTrimEndTimeUs, sentMediaQuality.getCode());
return new TransformProperties(existing.skipTransform, existing.videoTrim, existing.videoTrimStartTimeUs, existing.videoTrimEndTimeUs, sentMediaQuality.getCode(), existing.mp4Faststart);
}
public boolean shouldSkipTransform() {
@ -1723,6 +1734,10 @@ public class AttachmentTable extends DatabaseTable {
return videoTrim;
}
public boolean isMp4Faststart() {
return mp4Faststart;
}
public long getVideoTrimStartTimeUs() {
return videoTrimStartTimeUs;
}
@ -1736,9 +1751,12 @@ public class AttachmentTable extends DatabaseTable {
}
@NonNull TransformProperties withSkipTransform() {
return new TransformProperties(true, false, 0, 0, sentMediaQuality);
return new TransformProperties(true, false, 0, 0, sentMediaQuality, false);
}
@NonNull TransformProperties withMp4Faststart() {
return new TransformProperties(skipTransform, videoTrim, videoTrimStartTimeUs, videoTrimEndTimeUs, sentMediaQuality, true);
}
public @NonNull String serialize() {
return JsonUtil.toJson(this);
}

View file

@ -174,10 +174,10 @@ public final class AttachmentCompressionJob extends BaseJob {
try (MediaStream converted = compressImage(context, attachment, constraints)) {
attachmentDatabase.updateAttachmentData(attachment, converted, false);
}
attachmentDatabase.markAttachmentAsTransformed(attachmentId);
attachmentDatabase.markAttachmentAsTransformed(attachmentId, false);
} else if (constraints.isSatisfied(context, attachment)) {
Log.i(TAG, "Not compressing.");
attachmentDatabase.markAttachmentAsTransformed(attachmentId);
attachmentDatabase.markAttachmentAsTransformed(attachmentId, false);
} else {
throw new UndeliverableMessageException("Size constraints could not be met!");
}
@ -253,7 +253,7 @@ public final class AttachmentCompressionJob extends BaseJob {
}
}
attachmentDatabase.markAttachmentAsTransformed(attachment.getAttachmentId());
attachmentDatabase.markAttachmentAsTransformed(attachment.getAttachmentId(), false);
return Objects.requireNonNull(attachmentDatabase.getAttachment(attachment.getAttachmentId()));
} else {
@ -276,7 +276,7 @@ public final class AttachmentCompressionJob extends BaseJob {
attachmentDatabase.updateAttachmentData(attachment, mediaStream, true);
}
attachmentDatabase.markAttachmentAsTransformed(attachment.getAttachmentId());
attachmentDatabase.markAttachmentAsTransformed(attachment.getAttachmentId(), true);
return Objects.requireNonNull(attachmentDatabase.getAttachment(attachment.getAttachmentId()));
} else {

View file

@ -177,6 +177,7 @@ class AttachmentUploadJob private constructor(
.withVoiceNote(attachment.isVoiceNote)
.withBorderless(attachment.isBorderless)
.withGif(attachment.isVideoGif)
.withFaststart(attachment.transformProperties.isMp4Faststart)
.withWidth(attachment.width)
.withHeight(attachment.height)
.withUploadTimestamp(System.currentTimeMillis())

View file

@ -193,6 +193,7 @@ public final class LegacyAttachmentUploadJob extends BaseJob {
.withVoiceNote(attachment.isVoiceNote())
.withBorderless(attachment.isBorderless())
.withGif(attachment.isVideoGif())
.withFaststart(attachment.getTransformProperties().isMp4Faststart())
.withWidth(attachment.getWidth())
.withHeight(attachment.getHeight())
.withUploadTimestamp(System.currentTimeMillis())

View file

@ -207,6 +207,7 @@ public abstract class PushSendJob extends SendJob {
.withVoiceNote(attachment.isVoiceNote())
.withBorderless(attachment.isBorderless())
.withGif(attachment.isVideoGif())
.withFaststart(attachment.getTransformProperties().isMp4Faststart())
.withWidth(attachment.getWidth())
.withHeight(attachment.getHeight())
.withCaption(attachment.getCaption())

View file

@ -33,6 +33,6 @@ public final class VideoTrimTransform implements MediaTransform {
media.isVideoGif(),
media.getBucketId(),
media.getCaption(),
Optional.of(new AttachmentTable.TransformProperties(false, data.durationEdited, data.startTimeUs, data.endTimeUs, SentMediaQuality.STANDARD.getCode())));
Optional.of(new AttachmentTable.TransformProperties(false, data.durationEdited, data.startTimeUs, data.endTimeUs, SentMediaQuality.STANDARD.getCode(), false)));
}
}

View file

@ -339,7 +339,7 @@ object Stories {
error("Illegal clip: $startTimeUs > $endTimeUs for clip $clipIndex")
}
AttachmentTable.TransformProperties(false, true, startTimeUs, endTimeUs, SentMediaQuality.STANDARD.code)
AttachmentTable.TransformProperties(false, true, startTimeUs, endTimeUs, SentMediaQuality.STANDARD.code, false)
}.map { transformMedia(media, it) }
}

View file

@ -11,13 +11,13 @@ public class AttachmentDatabaseTransformPropertiesTest {
public void transformProperties_verifyStructure() {
AttachmentTable.TransformProperties properties = AttachmentTable.TransformProperties.empty();
assertEquals("Added transform property, need to confirm default behavior for pre-existing payloads in database",
"{\"skipTransform\":false,\"videoTrim\":false,\"videoTrimStartTimeUs\":0,\"videoTrimEndTimeUs\":0,\"sentMediaQuality\":0,\"videoEdited\":false}",
"{\"skipTransform\":false,\"videoTrim\":false,\"videoTrimStartTimeUs\":0,\"videoTrimEndTimeUs\":0,\"sentMediaQuality\":0,\"mp4Faststart\":false,\"videoEdited\":false}",
properties.serialize());
}
@Test
public void transformProperties_verifyMissingSentMediaQualityDefaultBehavior() {
String json = "{\"skipTransform\":false,\"videoTrim\":false,\"videoTrimStartTimeUs\":0,\"videoTrimEndTimeUs\":0,\"videoEdited\":false}";
String json = "{\"skipTransform\":false,\"videoTrim\":false,\"videoTrimStartTimeUs\":0,\"videoTrimEndTimeUs\":0,\"videoEdited\":false,\"mp4Faststart\":false}";
AttachmentTable.TransformProperties properties = AttachmentTable.TransformProperties.parse(json);

View file

@ -85,7 +85,7 @@ class UploadDependencyGraphTest {
UriAttachmentBuilder.build(
id = 10,
contentType = MediaUtil.IMAGE_JPEG,
transformProperties = AttachmentTable.TransformProperties(false, true, increment, increment + 1, SentMediaQuality.STANDARD.code)
transformProperties = AttachmentTable.TransformProperties(false, true, increment, increment + 1, SentMediaQuality.STANDARD.code, false)
)
}
@ -127,7 +127,7 @@ class UploadDependencyGraphTest {
UriAttachmentBuilder.build(
id = 10,
contentType = MediaUtil.IMAGE_JPEG,
transformProperties = if (it != 1) AttachmentTable.TransformProperties(false, true, 1, 2, SentMediaQuality.STANDARD.code) else null
transformProperties = if (it != 1) AttachmentTable.TransformProperties(false, true, 1, 2, SentMediaQuality.STANDARD.code, false) else null
)
}

View file

@ -792,6 +792,7 @@ public class SignalServiceMessageSender {
PushAttachmentData attachmentData = new PushAttachmentData(attachment.getContentType(),
dataStream,
ciphertextLength,
attachment.isFaststart(),
new AttachmentCipherOutputStreamFactory(attachmentKey, attachmentIV),
attachment.getListener(),
attachment.getCancelationSignal(),
@ -877,7 +878,7 @@ public class SignalServiceMessageSender {
attachment.getHeight(),
Optional.of(digest.getDigest()),
Optional.ofNullable(digest.getIncrementalDigest()),
digest.getIncrementalMacChunkSize(),
digest.getIncrementalDigest() != null ? digest.getIncrementalMacChunkSize() : 0,
attachment.getFileName(),
attachment.getVoiceNote(),
attachment.isBorderless(),

View file

@ -41,7 +41,7 @@ public abstract class SignalServiceAttachment {
}
public static SignalServiceAttachmentStream emptyStream(String contentType) {
return new SignalServiceAttachmentStream(new ByteArrayInputStream(new byte[0]), contentType, 0, Optional.empty(), false, false, false, null, null);
return new SignalServiceAttachmentStream(new ByteArrayInputStream(new byte[0]), contentType, 0, Optional.empty(), false, false, false, false, null, null);
}
public static class Builder {
@ -55,6 +55,7 @@ public abstract class SignalServiceAttachment {
private boolean voiceNote;
private boolean borderless;
private boolean gif;
private boolean faststart;
private int width;
private int height;
private String caption;
@ -109,6 +110,11 @@ public abstract class SignalServiceAttachment {
return this;
}
public Builder withFaststart(boolean faststart) {
this.faststart = faststart;
return this;
}
public Builder withWidth(int width) {
this.width = width;
return this;
@ -151,6 +157,7 @@ public abstract class SignalServiceAttachment {
voiceNote,
borderless,
gif,
faststart,
Optional.empty(),
width,
height,

View file

@ -29,6 +29,7 @@ public class SignalServiceAttachmentStream extends SignalServiceAttachment imple
private final boolean voiceNote;
private final boolean borderless;
private final boolean gif;
private final boolean faststart;
private final int width;
private final int height;
private final long uploadTimestamp;
@ -43,10 +44,11 @@ public class SignalServiceAttachmentStream extends SignalServiceAttachment imple
boolean voiceNote,
boolean borderless,
boolean gif,
boolean faststart,
ProgressListener listener,
CancelationSignal cancelationSignal)
{
this(inputStream, contentType, length, fileName, voiceNote, borderless, gif, Optional.empty(), 0, 0, System.currentTimeMillis(), Optional.empty(), Optional.empty(), listener, cancelationSignal, Optional.empty());
this(inputStream, contentType, length, fileName, voiceNote, borderless, gif, faststart, Optional.empty(), 0, 0, System.currentTimeMillis(), Optional.empty(), Optional.empty(), listener, cancelationSignal, Optional.empty());
}
public SignalServiceAttachmentStream(InputStream inputStream,
@ -56,6 +58,7 @@ public class SignalServiceAttachmentStream extends SignalServiceAttachment imple
boolean voiceNote,
boolean borderless,
boolean gif,
boolean faststart,
Optional<byte[]> preview,
int width,
int height,
@ -67,21 +70,22 @@ public class SignalServiceAttachmentStream extends SignalServiceAttachment imple
Optional<ResumableUploadSpec> resumableUploadSpec)
{
super(contentType);
this.inputStream = inputStream;
this.length = length;
this.fileName = fileName;
this.listener = listener;
this.voiceNote = voiceNote;
this.borderless = borderless;
this.gif = gif;
this.preview = preview;
this.width = width;
this.height = height;
this.uploadTimestamp = uploadTimestamp;
this.caption = caption;
this.blurHash = blurHash;
this.cancelationSignal = cancelationSignal;
this.resumableUploadSpec = resumableUploadSpec;
this.inputStream = inputStream;
this.length = length;
this.fileName = fileName;
this.listener = listener;
this.voiceNote = voiceNote;
this.borderless = borderless;
this.gif = gif;
this.preview = preview;
this.faststart = faststart;
this.width = width;
this.height = height;
this.uploadTimestamp = uploadTimestamp;
this.caption = caption;
this.blurHash = blurHash;
this.cancelationSignal = cancelationSignal;
this.resumableUploadSpec = resumableUploadSpec;
}
@Override
@ -130,6 +134,10 @@ public class SignalServiceAttachmentStream extends SignalServiceAttachment imple
return gif;
}
public boolean isFaststart() {
return faststart;
}
public int getWidth() {
return width;
}

View file

@ -61,7 +61,7 @@ public class DeviceContactsInputStream extends ChunkedInputStream {
InputStream avatarStream = new LimitedInputStream(in, avatarLength);
String avatarContentType = details.avatar.contentType;
avatar = Optional.of(new SignalServiceAttachmentStream(avatarStream, avatarContentType, avatarLength, Optional.empty(), false, false, false, null, null));
avatar = Optional.of(new SignalServiceAttachmentStream(avatarStream, avatarContentType, avatarLength, Optional.empty(), false, false, false, false, null, null));
}
if (details.verified != null) {

View file

@ -18,19 +18,21 @@ public class PushAttachmentData {
private final String contentType;
private final InputStream data;
private final long dataSize;
private final boolean incremental;
private final OutputStreamFactory outputStreamFactory;
private final ProgressListener listener;
private final CancelationSignal cancelationSignal;
private final ResumableUploadSpec resumableUploadSpec;
public PushAttachmentData(String contentType, InputStream data, long dataSize,
OutputStreamFactory outputStreamFactory,
boolean incremental, OutputStreamFactory outputStreamFactory,
ProgressListener listener, CancelationSignal cancelationSignal,
ResumableUploadSpec resumableUploadSpec)
{
this.contentType = contentType;
this.data = data;
this.dataSize = dataSize;
this.incremental = incremental;
this.outputStreamFactory = outputStreamFactory;
this.resumableUploadSpec = resumableUploadSpec;
this.listener = listener;
@ -49,6 +51,10 @@ public class PushAttachmentData {
return dataSize;
}
public boolean getIncremental() {
return incremental;
}
public OutputStreamFactory getOutputStreamFactory() {
return outputStreamFactory;
}

View file

@ -946,7 +946,7 @@ public class PushServiceSocket {
formAttributes.getPolicy(), formAttributes.getAlgorithm(),
formAttributes.getCredential(), formAttributes.getDate(),
formAttributes.getSignature(), profileAvatar.getData(),
profileAvatar.getContentType(), profileAvatar.getDataLength(),
profileAvatar.getContentType(), profileAvatar.getDataLength(), false,
profileAvatar.getOutputStreamFactory(), null, null);
return Optional.of(formAttributes.getKey());
@ -1393,7 +1393,7 @@ public class PushServiceSocket {
uploadAttributes.credential, uploadAttributes.date,
uploadAttributes.signature,
new ByteArrayInputStream(avatarCipherText),
"application/octet-stream", avatarCipherText.length,
"application/octet-stream", avatarCipherText.length, false,
new NoCipherOutputStreamFactory(),
null, null);
}
@ -1407,8 +1407,8 @@ public class PushServiceSocket {
uploadAttributes.getCredential(), uploadAttributes.getDate(),
uploadAttributes.getSignature(), attachment.getData(),
"application/octet-stream", attachment.getDataSize(),
attachment.getOutputStreamFactory(), attachment.getListener(),
attachment.getCancelationSignal());
attachment.getIncremental(), attachment.getOutputStreamFactory(),
attachment.getListener(), attachment.getCancelationSignal());
return new Pair<>(id, digest);
}
@ -1434,6 +1434,7 @@ public class PushServiceSocket {
attachment.getData(),
"application/octet-stream",
attachment.getDataSize(),
attachment.getIncremental(),
attachment.getOutputStreamFactory(),
attachment.getListener(),
attachment.getCancelationSignal());
@ -1442,6 +1443,7 @@ public class PushServiceSocket {
attachment.getData(),
"application/offset+octet-stream",
attachment.getDataSize(),
attachment.getIncremental(),
attachment.getOutputStreamFactory(),
attachment.getListener(),
attachment.getCancelationSignal(),
@ -1529,7 +1531,7 @@ public class PushServiceSocket {
private AttachmentDigest uploadToCdn0(String path, String acl, String key, String policy, String algorithm,
String credential, String date, String signature,
InputStream data, String contentType, long length,
InputStream data, String contentType, long length, boolean incremental,
OutputStreamFactory outputStreamFactory, ProgressListener progressListener,
CancelationSignal cancelationSignal)
throws PushNetworkException, NonSuccessfulResponseCodeException
@ -1541,7 +1543,7 @@ public class PushServiceSocket {
.readTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
.build();
DigestingRequestBody file = new DigestingRequestBody(data, outputStreamFactory, contentType, length, progressListener, cancelationSignal, 0);
DigestingRequestBody file = new DigestingRequestBody(data, outputStreamFactory, contentType, length, incremental, progressListener, cancelationSignal, 0);
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
@ -1639,7 +1641,7 @@ public class PushServiceSocket {
}
}
private AttachmentDigest uploadToCdn2(String resumableUrl, InputStream data, String contentType, long length, OutputStreamFactory outputStreamFactory, ProgressListener progressListener, CancelationSignal cancelationSignal) throws IOException {
private AttachmentDigest uploadToCdn2(String resumableUrl, InputStream data, String contentType, long length, boolean incremental, OutputStreamFactory outputStreamFactory, ProgressListener progressListener, CancelationSignal cancelationSignal) throws IOException {
ConnectionHolder connectionHolder = getRandom(cdnClientsMap.get(2), random);
OkHttpClient okHttpClient = connectionHolder.getClient()
.newBuilder()
@ -1648,7 +1650,7 @@ public class PushServiceSocket {
.build();
ResumeInfo resumeInfo = getResumeInfoCdn2(resumableUrl, length);
DigestingRequestBody file = new DigestingRequestBody(data, outputStreamFactory, contentType, length, progressListener, cancelationSignal, resumeInfo.contentStart);
DigestingRequestBody file = new DigestingRequestBody(data, outputStreamFactory, contentType, length, incremental, progressListener, cancelationSignal, resumeInfo.contentStart);
if (resumeInfo.contentStart == length) {
Log.w(TAG, "Resume start point == content length");
@ -1690,6 +1692,7 @@ public class PushServiceSocket {
InputStream data,
String contentType,
long length,
boolean incremental,
OutputStreamFactory outputStreamFactory,
ProgressListener progressListener,
CancelationSignal cancelationSignal,
@ -1704,7 +1707,7 @@ public class PushServiceSocket {
.build();
ResumeInfo resumeInfo = getResumeInfoCdn3(resumableUrl, headers);
DigestingRequestBody file = new DigestingRequestBody(data, outputStreamFactory, contentType, length, progressListener, cancelationSignal, resumeInfo.contentStart);
DigestingRequestBody file = new DigestingRequestBody(data, outputStreamFactory, contentType, length, incremental, progressListener, cancelationSignal, resumeInfo.contentStart);
if (resumeInfo.contentStart == length) {
Log.w(TAG, "Resume start point == content length");

View file

@ -22,6 +22,7 @@ class DigestingRequestBody(
private val outputStreamFactory: OutputStreamFactory,
private val contentType: String,
private val contentLength: Long,
private val incremental: Boolean,
private val progressListener: SignalServiceAttachment.ProgressListener?,
private val cancelationSignal: CancelationSignal?,
private val contentStart: Long
@ -41,7 +42,7 @@ class DigestingRequestBody(
override fun writeTo(sink: BufferedSink) {
val digestStream = ByteArrayOutputStream()
val inner = SkippingOutputStream(contentStart, sink.outputStream())
val isIncremental = outputStreamFactory is AttachmentCipherOutputStreamFactory
val isIncremental = incremental && outputStreamFactory is AttachmentCipherOutputStreamFactory
val sizeChoice: ChunkSizeChoice = ChunkSizeChoice.inferChunkSize(contentLength.toInt())
val outputStream: DigestingOutputStream = if (isIncremental) {
(outputStreamFactory as AttachmentCipherOutputStreamFactory).createIncrementalFor(inner, contentLength, sizeChoice, digestStream)

View file

@ -83,7 +83,7 @@ public class DigestingRequestBodyTest {
}
private DigestingRequestBody getBody(long contentStart) {
return new DigestingRequestBody(new ByteArrayInputStream(input), outputStreamFactory, "application/octet", CONTENT_LENGTH, new SignalServiceAttachment.ProgressListener() {
return new DigestingRequestBody(new ByteArrayInputStream(input), outputStreamFactory, "application/octet", CONTENT_LENGTH, false, new SignalServiceAttachment.ProgressListener() {
@Override
public void onAttachmentProgress(long total, long progress) {
// no-op