improved size limit

This commit is contained in:
Mariotaku Lee 2017-01-23 21:31:14 +08:00
parent 39ea713dd9
commit 184623fd0e
No known key found for this signature in database
GPG Key ID: 9C0706AE47FCE2AD
4 changed files with 288 additions and 34 deletions

View File

@ -1,9 +1,222 @@
package org.mariotaku.twidere.model.account;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
/**
* Created by mariotaku on 16/2/26.
*/
public interface AccountExtras extends Parcelable {
@JsonObject
class ImageLimit {
@JsonField(name = "max_width")
int maxWidth;
@JsonField(name = "max_height")
int maxHeight;
public int getMaxWidth() {
return maxWidth;
}
public void setMaxWidth(int maxWidth) {
this.maxWidth = maxWidth;
}
public int getMaxHeight() {
return maxHeight;
}
public void setMaxHeight(int maxHeight) {
this.maxHeight = maxHeight;
}
@NonNull
public static ImageLimit ofSize(int width, int height) {
final ImageLimit limit = new ImageLimit();
limit.setMaxWidth(width);
limit.setMaxHeight(height);
return limit;
}
}
@JsonObject
class VideoLimit {
@JsonField(name = "min_width")
int minWidth;
@JsonField(name = "min_height")
int minHeight;
@JsonField(name = "max_width")
int maxWidth;
@JsonField(name = "max_height")
int maxHeight;
@JsonField(name = "can_rotate_geometry_limit")
boolean canRotateGeometryLimit;
@JsonField(name = "max_size_sync")
long maxSizeSync;
@JsonField(name = "max_size_async")
long maxSizeAsync;
@JsonField(name = "min_aspect_ratio")
double minAspectRatio;
@JsonField(name = "max_aspect_ratio")
double maxAspectRatio;
@JsonField(name = "min_frame_rate")
double minFrameRate;
@JsonField(name = "max_frame_rate")
double maxFrameRate;
public int getMinWidth() {
return minWidth;
}
public void setMinWidth(int minWidth) {
this.minWidth = minWidth;
}
public int getMinHeight() {
return minHeight;
}
public void setMinHeight(int minHeight) {
this.minHeight = minHeight;
}
public int getMaxWidth() {
return maxWidth;
}
public void setMaxWidth(int maxWidth) {
this.maxWidth = maxWidth;
}
public int getMaxHeight() {
return maxHeight;
}
public void setMaxHeight(int maxHeight) {
this.maxHeight = maxHeight;
}
public boolean canRotateGeometryLimit() {
return canRotateGeometryLimit;
}
public void setCanRotateGeometryLimit(boolean canRotateGeometryLimit) {
this.canRotateGeometryLimit = canRotateGeometryLimit;
}
public long getMaxSizeSync() {
return maxSizeSync;
}
public void setMaxSizeSync(long maxSizeSync) {
this.maxSizeSync = maxSizeSync;
}
public long getMaxSizeAsync() {
return maxSizeAsync;
}
public void setMaxSizeAsync(long maxSizeAsync) {
this.maxSizeAsync = maxSizeAsync;
}
public double getMinAspectRatio() {
return minAspectRatio;
}
public void setMinAspectRatio(double minAspectRatio) {
this.minAspectRatio = minAspectRatio;
}
public double getMaxAspectRatio() {
return maxAspectRatio;
}
public void setMaxAspectRatio(double maxAspectRatio) {
this.maxAspectRatio = maxAspectRatio;
}
public double getMinFrameRate() {
return minFrameRate;
}
public void setMinFrameRate(double minFrameRate) {
this.minFrameRate = minFrameRate;
}
public double getMaxFrameRate() {
return maxFrameRate;
}
public void setMaxFrameRate(double maxFrameRate) {
this.maxFrameRate = maxFrameRate;
}
@SuppressWarnings("RedundantIfStatement")
public boolean checkGeometry(int width, int height) {
// Check w & h
boolean widthValid = inRange(width, getMinWidth(), getMaxWidth());
boolean heightValid = inRange(height, getMinHeight(), getMaxHeight());
if (canRotateGeometryLimit()) {
widthValid |= inRange(height, getMinWidth(), getMaxWidth());
heightValid |= inRange(width, getMinHeight(), getMaxHeight());
}
if (!widthValid || !heightValid) {
return false;
}
// Check aspect ratio
double aspectRatio = width / (double) height;
if (!inRange(aspectRatio, getMinAspectRatio(), getMaxAspectRatio())) {
return false;
}
return true;
}
public boolean checkFrameRate(double frameRate) {
return inRange(frameRate, getMinFrameRate(), getMaxFrameRate());
}
@SuppressWarnings("RedundantIfStatement")
private boolean inRange(int num, int min, int max) {
if (min > 0 && num < min) return false;
if (max > 0 && num > max) return false;
return true;
}
@SuppressWarnings("RedundantIfStatement")
private boolean inRange(double num, double min, double max) {
if (min > 0 && num < min) return false;
if (max > 0 && num > max) return false;
return true;
}
public static VideoLimit twitterDefault() {
VideoLimit videoLimit = new VideoLimit();
videoLimit.setMinWidth(32);
videoLimit.setMinHeight(32);
videoLimit.setMaxWidth(1280);
videoLimit.setMaxHeight(1024);
videoLimit.setCanRotateGeometryLimit(true);
videoLimit.setMinAspectRatio(1.0 / 3);
videoLimit.setMaxAspectRatio(3);
videoLimit.setMaxSizeSync(15 * 1024 * 1024);
videoLimit.setMaxSizeAsync(512 * 1024 * 1024);
videoLimit.setMinFrameRate(0);
videoLimit.setMaxFrameRate(40);
return videoLimit;
}
}
}

View File

@ -20,10 +20,10 @@ fun String?.toInt(def: Int): Int {
}
}
fun String.toDoubleOrNull(): Double? {
fun String?.toDouble(def: Double): Double {
try {
return toDouble()
return this?.toDouble() ?: def
} catch (e: NumberFormatException) {
return null
return def
}
}

View File

@ -38,13 +38,12 @@ import kotlinx.android.synthetic.main.activity_link_handler.*
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.convert
import org.mariotaku.ktextension.set
import org.mariotaku.ktextension.toDoubleOrNull
import org.mariotaku.ktextension.toDouble
import org.mariotaku.twidere.Constants.*
import org.mariotaku.twidere.R
import org.mariotaku.twidere.activity.iface.IControlBarActivity
import org.mariotaku.twidere.activity.iface.IControlBarActivity.ControlBarShowHideHelper
import org.mariotaku.twidere.constant.*
import org.mariotaku.twidere.constant.IntentConstants.*
import org.mariotaku.twidere.fragment.*
import org.mariotaku.twidere.fragment.filter.FiltersFragment
import org.mariotaku.twidere.fragment.filter.FiltersImportBlocksFragment
@ -494,8 +493,8 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowsInsetsCallback, IContro
LINK_ID_MAP -> {
isAccountIdRequired = false
if (!args.containsKey(EXTRA_LATITUDE) && !args.containsKey(EXTRA_LONGITUDE)) {
val lat = uri.getQueryParameter(QUERY_PARAM_LAT).toDoubleOrNull() ?: return null
val lng = uri.getQueryParameter(QUERY_PARAM_LNG).toDoubleOrNull() ?: return null
val lat = uri.getQueryParameter(QUERY_PARAM_LAT).toDouble(Double.NaN)
val lng = uri.getQueryParameter(QUERY_PARAM_LNG).toDouble(Double.NaN)
if (lat.isNaN() || lng.isNaN()) return null
args.putDouble(EXTRA_LATITUDE, lat)
args.putDouble(EXTRA_LONGITUDE, lng)

View File

@ -6,6 +6,7 @@ import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Point
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
import android.support.annotation.UiThread
@ -22,6 +23,9 @@ import org.apache.commons.lang3.ArrayUtils
import org.apache.commons.lang3.math.NumberUtils
import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.ktextension.addAllTo
import org.mariotaku.ktextension.toDouble
import org.mariotaku.ktextension.toInt
import org.mariotaku.ktextension.toLong
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.fanfou.model.PhotoStatusUpdate
@ -39,6 +43,7 @@ import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.app.TwidereApplication
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.account.AccountExtras
import org.mariotaku.twidere.model.analyzer.UpdateStatus
import org.mariotaku.twidere.model.draft.UpdateStatusActionExtras
import org.mariotaku.twidere.model.util.ParcelableLocationUtils
@ -271,7 +276,7 @@ class UpdateStatusTask(
result.exceptions[i] = MicroBlogException(
context.getString(R.string.error_too_many_photos_fanfou))
} else {
val sizeLimit = SizeLimit(width = 2048, height = 1536)
val sizeLimit = getSizeLimit(account)
mediaBody = getBodyFromMedia(context, mediaLoader,
Uri.parse(statusUpdate.media[0].uri),
sizeLimit, statusUpdate.media[0].type,
@ -332,7 +337,8 @@ class UpdateStatusTask(
if (pendingUpdate.sharedMediaIds != null) {
mediaIds = pendingUpdate.sharedMediaIds
} else {
val (ids, deleteOnSuccess, deleteAlways) = uploadAllMediaShared(upload, update, ownerIds, true)
val (ids, deleteOnSuccess, deleteAlways) = uploadAllMediaShared(upload,
account, update, ownerIds, true)
mediaIds = ids
deleteOnSuccess.addAllTo(pendingUpdate.deleteOnSuccess)
deleteAlways.addAllTo(pendingUpdate.deleteAlways)
@ -346,7 +352,8 @@ class UpdateStatusTask(
AccountType.STATUSNET -> {
// TODO use their native API
val upload = account.newMicroBlogInstance(context, cls = TwitterUpload::class.java)
val (ids, deleteOnSuccess, deleteAlways) = uploadAllMediaShared(upload, update, ownerIds, false)
val (ids, deleteOnSuccess, deleteAlways) = uploadAllMediaShared(upload, account,
update, ownerIds, false)
mediaIds = ids
deleteOnSuccess.addAllTo(pendingUpdate.deleteOnSuccess)
deleteAlways.addAllTo(pendingUpdate.deleteAlways)
@ -456,9 +463,9 @@ class UpdateStatusTask(
@Throws(UploadException::class)
private fun uploadAllMediaShared(
upload: TwitterUpload,
account: AccountDetails,
update: ParcelableStatusUpdate,
ownerIds: Array<String>,
chucked: Boolean
ownerIds: Array<String>, chucked: Boolean
): SharedMediaUploadResult {
val deleteOnSuccess = ArrayList<MediaDeletionItem>()
val deleteAlways = ArrayList<MediaDeletionItem>()
@ -467,7 +474,7 @@ class UpdateStatusTask(
//noinspection TryWithIdenticalCatches
var body: MediaStreamBody? = null
try {
val sizeLimit = SizeLimit(width = 2048, height = 1536)
val sizeLimit = getSizeLimit(account)
body = getBodyFromMedia(context, mediaLoader, Uri.parse(media.uri), sizeLimit,
media.type, ContentLengthInputStream.ReadListener { length, position ->
stateCallback.onUploadingProgressChanged(index, position, length)
@ -558,7 +565,6 @@ class UpdateStatusTask(
}
}
private fun saveDraft(@Draft.Action draftAction: String?, statusUpdate: ParcelableStatusUpdate): Long {
val draft = Draft()
draft.unique_id = statusUpdate.draft_unique_id ?: UUID.randomUUID().toString()
@ -580,6 +586,12 @@ class UpdateStatusTask(
return NumberUtils.toLong(draftUri.lastPathSegment, -1)
}
private fun getSizeLimit(details: AccountDetails): SizeLimit {
val imageLimit = AccountExtras.ImageLimit.ofSize(2048, 1536)
val videoLimit = AccountExtras.VideoLimit.twitterDefault()
return SizeLimit(imageLimit, videoLimit)
}
internal class PendingStatusUpdate(val length: Int, defaultText: String) {
constructor(statusUpdate: ParcelableStatusUpdate) : this(statusUpdate.accounts.size,
@ -705,8 +717,8 @@ class UpdateStatusTask(
}
data class SizeLimit(
val width: Int,
val height: Int
val image: AccountExtras.ImageLimit,
val video: AccountExtras.VideoLimit
)
data class MediaStreamBody(
@ -770,20 +782,15 @@ class UpdateStatusTask(
}
return@run null
}
val data = run {
if (sizeLimit != null) {
when (type) {
ParcelableMedia.Type.IMAGE -> {
return@run imageStream(context, resolver, mediaLoader, mediaUri,
mediaType, sizeLimit)
}
ParcelableMedia.Type.VIDEO -> {
return@run videoStream(context, resolver, mediaUri, mediaType)
}
}
}
return@run null
}
val data = if (sizeLimit != null) when (type) {
ParcelableMedia.Type.IMAGE -> imageStream(context, resolver, mediaLoader, mediaUri,
mediaType, sizeLimit)
ParcelableMedia.Type.VIDEO -> videoStream(context, resolver, mediaUri, mediaType,
sizeLimit)
else -> null
} else null
val cis = data?.stream ?: run {
val st = resolver.openInputStream(mediaUri) ?: throw FileNotFoundException(mediaUri.toString())
val length = st.available().toLong()
@ -816,8 +823,9 @@ class UpdateStatusTask(
mediaType = o.outMimeType
}
val size = Point(o.outWidth, o.outHeight)
val imageLimit = sizeLimit.image
o.inSampleSize = Utils.calculateInSampleSize(o.outWidth, o.outHeight,
sizeLimit.width, sizeLimit.height)
imageLimit.maxWidth, imageLimit.maxHeight)
o.inJustDecodeBounds = false
if (o.outWidth > 0 && o.outHeight > 0 && mediaType != "image/gif") {
val displayOptions = DisplayImageOptions.Builder()
@ -852,8 +860,42 @@ class UpdateStatusTask(
context: Context,
resolver: ContentResolver,
mediaUri: Uri,
defaultType: String?
defaultType: String?,
sizeLimit: SizeLimit
): MediaStreamData? {
var mediaType = defaultType
val videoLimit = sizeLimit.video
val geometry = Point()
var duration = -1L
var framerate = -1.0
// TODO only transcode video if needed, use `MediaMetadataRetriever`
val retriever = MediaMetadataRetriever()
try {
retriever.setDataSource(context, mediaUri)
val extractedMimeType = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE)
if (extractedMimeType != null) {
mediaType = extractedMimeType
}
geometry.x = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH).toInt(-1)
geometry.y = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT).toInt(-1)
duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong(-1)
framerate = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE).toDouble(-1.0)
} catch (e: Exception) {
// Ignore
} finally {
try {
retriever.release()
} catch (e: Exception) {
// Ignore
}
}
if (videoLimit.checkGeometry(geometry.x, geometry.y)
&& videoLimit.checkFrameRate(framerate)) {
// Size valid, upload directly
return null
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
return null
}
@ -883,8 +925,8 @@ class UpdateStatusTask(
tempFile.delete()
return null
}
return MediaStreamData(ContentLengthInputStream(tempFile.inputStream(),
tempFile.length()), defaultType, null, null, listOf(FileMediaDeletionItem(tempFile)))
return MediaStreamData(ContentLengthInputStream(tempFile.inputStream(), tempFile.length()),
mediaType, geometry, null, listOf(FileMediaDeletionItem(tempFile)))
}
internal class MediaStreamData(