mirror of
https://github.com/TwidereProject/Twidere-Android
synced 2025-02-17 04:00:48 +01:00
improved size limit
This commit is contained in:
parent
39ea713dd9
commit
184623fd0e
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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(
|
||||
|
Loading…
x
Reference in New Issue
Block a user