Merge pull request #1051 from davotoula/configurable-media-compression

Configurable media compression
This commit is contained in:
Vitor Pamplona
2024-09-05 12:07:33 -04:00
committed by GitHub
15 changed files with 213 additions and 9 deletions
@@ -331,8 +331,8 @@ fun EditPostView(
ImageVideoDescription( ImageVideoDescription(
url, url,
accountViewModel.account.settings.defaultFileServer, accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent -> onAdd = { alt, server, sensitiveContent, mediaQuality ->
postViewModel.upload(url, alt, sensitiveContent, false, server, accountViewModel::toast, context) postViewModel.upload(url, alt, sensitiveContent, mediaQuality, false, server, accountViewModel::toast, context)
if (!server.isNip95) { if (!server.isNip95) {
accountViewModel.account.settings.changeDefaultFileServer(server.server) accountViewModel.account.settings.changeDefaultFileServer(server.server)
} }
@@ -148,6 +148,7 @@ open class EditPostViewModel : ViewModel() {
galleryUri: Uri, galleryUri: Uri,
alt: String?, alt: String?,
sensitiveContent: Boolean, sensitiveContent: Boolean,
mediaQuality: Int,
isPrivate: Boolean = false, isPrivate: Boolean = false,
server: ServerOption, server: ServerOption,
onError: (String, String) -> Unit, onError: (String, String) -> Unit,
@@ -223,6 +224,7 @@ open class EditPostViewModel : ViewModel() {
isUploadingImage = false isUploadingImage = false
onError(stringRes(context, R.string.failed_to_upload_media_no_details), stringRes(context, it)) onError(stringRes(context, R.string.failed_to_upload_media_no_details), stringRes(context, it))
}, },
mediaQuality = MediaCompressor().intToCompressorQuality(mediaQuality),
) )
} }
} }
@@ -79,6 +79,7 @@ open class NewMediaModel : ViewModel() {
fun upload( fun upload(
context: Context, context: Context,
relayList: List<RelaySetupInfo>? = null, relayList: List<RelaySetupInfo>? = null,
mediaQuality: Int,
onError: (String) -> Unit = {}, onError: (String) -> Unit = {},
) { ) {
isUploadingImage = true isUploadingImage = true
@@ -166,6 +167,7 @@ open class NewMediaModel : ViewModel() {
uploadingDescription.value = null uploadingDescription.value = null
onError(stringRes(context, R.string.error_when_compressing_media, it)) onError(stringRes(context, R.string.error_when_compressing_media, it))
}, },
mediaQuality = MediaCompressor().intToCompressorQuality(mediaQuality),
) )
} }
} }
@@ -44,22 +44,26 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Slider
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
@@ -72,6 +76,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size5dp
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.events.FileServersEvent import com.vitorpamplona.quartz.events.FileServersEvent
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@@ -100,6 +105,7 @@ fun NewMediaView(
var showRelaysDialog by remember { mutableStateOf(false) } var showRelaysDialog by remember { mutableStateOf(false) }
var relayList = remember { accountViewModel.account.activeWriteRelays().toImmutableList() } var relayList = remember { accountViewModel.account.activeWriteRelays().toImmutableList() }
var mediaQualitySlider by remember { mutableIntStateOf(1) } // 0 = Low, 1 = Medium, 2 = High
Dialog( Dialog(
onDismissRequest = { onClose() }, onDismissRequest = { onClose() },
@@ -160,7 +166,7 @@ fun NewMediaView(
PostButton( PostButton(
onPost = { onPost = {
onClose() onClose()
postViewModel.upload(context, relayList) { postViewModel.upload(context, relayList, mediaQualitySlider) {
accountViewModel.toast(stringRes(context, R.string.failed_to_upload_media_no_details), it) accountViewModel.toast(stringRes(context, R.string.failed_to_upload_media_no_details), it)
} }
postViewModel.selectedServer?.let { postViewModel.selectedServer?.let {
@@ -180,6 +186,59 @@ fun NewMediaView(
modifier = Modifier.fillMaxWidth().verticalScroll(scrollState), modifier = Modifier.fillMaxWidth().verticalScroll(scrollState),
) { ) {
ImageVideoPost(postViewModel, accountViewModel) ImageVideoPost(postViewModel, accountViewModel)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier =
Modifier
.fillMaxWidth()
.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp))
.padding(vertical = 8.dp),
) {
Column(
modifier = Modifier.weight(1.0f),
verticalArrangement = Arrangement.spacedBy(Size5dp),
) {
Text(
text = stringRes(context, R.string.media_compression_quality_label),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
text = stringRes(context, R.string.media_compression_quality_explainer),
style = MaterialTheme.typography.bodySmall,
color = Color.Gray,
maxLines = 4,
overflow = TextOverflow.Ellipsis,
)
}
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box(modifier = Modifier.fillMaxWidth()) {
Text(
text =
when (mediaQualitySlider) {
0 -> stringRes(R.string.media_compression_quality_low)
1 -> stringRes(R.string.media_compression_quality_medium)
2 -> stringRes(R.string.media_compression_quality_high)
else -> stringRes(R.string.media_compression_quality_medium)
},
modifier = Modifier.align(Alignment.Center),
)
}
Slider(
value = mediaQualitySlider.toFloat(),
onValueChange = { mediaQualitySlider = it.toInt() },
valueRange = 0f..2f,
steps = 1,
)
}
}
} }
} }
} }
@@ -88,6 +88,7 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@@ -493,8 +494,8 @@ fun NewPostView(
ImageVideoDescription( ImageVideoDescription(
url, url,
accountViewModel.account.settings.defaultFileServer, accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent -> onAdd = { alt, server, sensitiveContent, mediaQuality ->
postViewModel.upload(url, alt, sensitiveContent, false, server, accountViewModel::toast, context) postViewModel.upload(url, alt, sensitiveContent, mediaQuality, false, server, accountViewModel::toast, context)
if (!server.isNip95) { if (!server.isNip95) {
accountViewModel.account.settings.changeDefaultFileServer(server.server) accountViewModel.account.settings.changeDefaultFileServer(server.server)
} }
@@ -1610,7 +1611,7 @@ fun CreateButton(
fun ImageVideoDescription( fun ImageVideoDescription(
uri: Uri, uri: Uri,
defaultServer: Nip96MediaServers.ServerName, defaultServer: Nip96MediaServers.ServerName,
onAdd: (String, ServerOption, Boolean) -> Unit, onAdd: (String, ServerOption, Boolean, Int) -> Unit,
onCancel: () -> Unit, onCancel: () -> Unit,
onError: (Int) -> Unit, onError: (Int) -> Unit,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
@@ -1668,6 +1669,7 @@ fun ImageVideoDescription(
} }
var message by remember { mutableStateOf("") } var message by remember { mutableStateOf("") }
var sensitiveContent by remember { mutableStateOf(false) } var sensitiveContent by remember { mutableStateOf(false) }
var mediaQualitySlider by remember { mutableIntStateOf(1) } // 0 = Low, 1 = Medium, 2 = High
Column( Column(
modifier = modifier =
@@ -1846,12 +1848,66 @@ fun ImageVideoDescription(
) )
} }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier =
Modifier
.fillMaxWidth()
.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp))
.padding(vertical = 8.dp),
) {
Column(
modifier = Modifier.weight(1.0f),
verticalArrangement = Arrangement.spacedBy(Size5dp),
) {
Text(
text = stringRes(R.string.media_compression_quality_label),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
text = stringRes(R.string.media_compression_quality_explainer),
style = MaterialTheme.typography.bodySmall,
color = Color.Gray,
maxLines = 4,
overflow = TextOverflow.Ellipsis,
)
}
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box(modifier = Modifier.fillMaxWidth()) {
Text(
text =
when (mediaQualitySlider) {
0 -> stringRes(R.string.media_compression_quality_low)
1 -> stringRes(R.string.media_compression_quality_medium)
2 -> stringRes(R.string.media_compression_quality_high)
else -> stringRes(R.string.media_compression_quality_medium)
},
modifier = Modifier.align(Alignment.Center),
)
}
Slider(
value = mediaQualitySlider.toFloat(),
onValueChange = { mediaQualitySlider = it.toInt() },
valueRange = 0f..2f,
steps = 1,
)
}
}
Button( Button(
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 10.dp), .padding(vertical = 10.dp),
onClick = { onAdd(message, selectedServer, sensitiveContent) }, onClick = { onAdd(message, selectedServer, sensitiveContent, mediaQualitySlider) },
shape = QuoteBorder, shape = QuoteBorder,
colors = colors =
ButtonDefaults.buttonColors( ButtonDefaults.buttonColors(
@@ -834,6 +834,7 @@ open class NewPostViewModel : ViewModel() {
galleryUri: Uri, galleryUri: Uri,
alt: String?, alt: String?,
sensitiveContent: Boolean, sensitiveContent: Boolean,
mediaQuality: Int,
isPrivate: Boolean = false, isPrivate: Boolean = false,
server: ServerOption, server: ServerOption,
onError: (title: String, message: String) -> Unit, onError: (title: String, message: String) -> Unit,
@@ -909,6 +910,7 @@ open class NewPostViewModel : ViewModel() {
isUploadingImage = false isUploadingImage = false
onError(stringRes(context, R.string.failed_to_upload_media_no_details), stringRes(context, it)) onError(stringRes(context, R.string.failed_to_upload_media_no_details), stringRes(context, it))
}, },
mediaQuality = MediaCompressor().intToCompressorQuality(mediaQuality),
) )
} }
} }
@@ -30,6 +30,7 @@ import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.service.Nip96Uploader import com.vitorpamplona.amethyst.service.Nip96Uploader
import com.vitorpamplona.amethyst.ui.components.CompressorQuality
import com.vitorpamplona.amethyst.ui.components.MediaCompressor import com.vitorpamplona.amethyst.ui.components.MediaCompressor
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.events.GitHubIdentity import com.vitorpamplona.quartz.events.GitHubIdentity
@@ -211,6 +212,8 @@ class NewUserMetadataViewModel : ViewModel() {
onError(stringRes(context, R.string.error_when_compressing_media), stringRes(context, it)) onError(stringRes(context, R.string.error_when_compressing_media), stringRes(context, it))
}, },
// Use MEDIUM quality as default
mediaQuality = CompressorQuality.MEDIUM,
) )
} }
} }
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.components
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import android.util.Log
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import androidx.core.net.toUri import androidx.core.net.toUri
import com.abedelazizshe.lightcompressorlibrary.CompressionListener import com.abedelazizshe.lightcompressorlibrary.CompressionListener
@@ -46,10 +47,20 @@ class MediaCompressor {
applicationContext: Context, applicationContext: Context,
onReady: (Uri, String?, Long?) -> Unit, onReady: (Uri, String?, Long?) -> Unit,
onError: (Int) -> Unit, onError: (Int) -> Unit,
mediaQuality: CompressorQuality,
) { ) {
checkNotInMainThread() checkNotInMainThread()
if (contentType?.startsWith("video", true) == true) { if (contentType?.startsWith("video", true) == true) {
val videoQuality =
when (mediaQuality) {
CompressorQuality.VERY_LOW -> VideoQuality.VERY_LOW
CompressorQuality.LOW -> VideoQuality.LOW
CompressorQuality.MEDIUM -> VideoQuality.MEDIUM
CompressorQuality.HIGH -> VideoQuality.HIGH
CompressorQuality.VERY_HIGH -> VideoQuality.VERY_HIGH
}
Log.d("MediaCompressor", "Using video compression $mediaQuality")
VideoCompressor.start( VideoCompressor.start(
// => This is required // => This is required
context = applicationContext, context = applicationContext,
@@ -65,7 +76,7 @@ class MediaCompressor {
appSpecificStorageConfiguration = AppSpecificStorageConfiguration(), appSpecificStorageConfiguration = AppSpecificStorageConfiguration(),
configureWith = configureWith =
Configuration( Configuration(
quality = VideoQuality.MEDIUM, quality = videoQuality,
// => required name // => required name
videoNames = listOf(UUID.randomUUID().toString()), videoNames = listOf(UUID.randomUUID().toString()),
), ),
@@ -110,10 +121,19 @@ class MediaCompressor {
!contentType.contains("gif") && !contentType.contains("gif") &&
!contentType.contains("svg") !contentType.contains("svg")
) { ) {
val imageQuality =
when (mediaQuality) {
CompressorQuality.VERY_LOW -> 40
CompressorQuality.LOW -> 50
CompressorQuality.MEDIUM -> 60
CompressorQuality.HIGH -> 80
CompressorQuality.VERY_HIGH -> 90
}
try { try {
Log.d("MediaCompressor", "Using image compression $mediaQuality")
val compressedImageFile = val compressedImageFile =
Compressor.compress(applicationContext, from(uri, contentType, applicationContext)) { Compressor.compress(applicationContext, from(uri, contentType, applicationContext)) {
default(width = 640, format = Bitmap.CompressFormat.JPEG) default(width = 640, format = Bitmap.CompressFormat.JPEG, quality = imageQuality)
} }
onReady(compressedImageFile.toUri(), contentType, compressedImageFile.length()) onReady(compressedImageFile.toUri(), contentType, compressedImageFile.length())
} catch (e: Exception) { } catch (e: Exception) {
@@ -162,4 +182,28 @@ class MediaCompressor {
} }
return arrayOf(name, extension) return arrayOf(name, extension)
} }
fun intToCompressorQuality(mediaQualityFloat: Int): CompressorQuality =
when (mediaQualityFloat) {
0 -> CompressorQuality.LOW
1 -> CompressorQuality.MEDIUM
2 -> CompressorQuality.HIGH
else -> CompressorQuality.MEDIUM
}
fun compressorQualityToInt(compressorQuality: CompressorQuality): Int =
when (compressorQuality) {
CompressorQuality.LOW -> 0
CompressorQuality.MEDIUM -> 1
CompressorQuality.HIGH -> 2
else -> 1
}
}
enum class CompressorQuality {
VERY_LOW,
LOW,
MEDIUM,
HIGH,
VERY_HIGH,
} }
@@ -116,7 +116,9 @@ import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
import com.vitorpamplona.amethyst.ui.actions.ServerOption import com.vitorpamplona.amethyst.ui.actions.ServerOption
import com.vitorpamplona.amethyst.ui.actions.UploadFromGallery import com.vitorpamplona.amethyst.ui.actions.UploadFromGallery
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation
import com.vitorpamplona.amethyst.ui.components.CompressorQuality
import com.vitorpamplona.amethyst.ui.components.LoadNote import com.vitorpamplona.amethyst.ui.components.LoadNote
import com.vitorpamplona.amethyst.ui.components.MediaCompressor
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
@@ -514,6 +516,8 @@ fun EditFieldRow(
galleryUri = it, galleryUri = it,
alt = null, alt = null,
sensitiveContent = false, sensitiveContent = false,
// Use MEDIUM quality
mediaQuality = MediaCompressor().compressorQualityToInt(CompressorQuality.MEDIUM),
server = ServerOption(accountViewModel.account.settings.defaultFileServer, false), server = ServerOption(accountViewModel.account.settings.defaultFileServer, false),
onError = accountViewModel::toast, onError = accountViewModel::toast,
context = context, context = context,
@@ -92,6 +92,8 @@ import com.vitorpamplona.amethyst.ui.actions.PostButton
import com.vitorpamplona.amethyst.ui.actions.ServerOption import com.vitorpamplona.amethyst.ui.actions.ServerOption
import com.vitorpamplona.amethyst.ui.actions.UploadFromGallery import com.vitorpamplona.amethyst.ui.actions.UploadFromGallery
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation
import com.vitorpamplona.amethyst.ui.components.CompressorQuality
import com.vitorpamplona.amethyst.ui.components.MediaCompressor
import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.navigation.TopBarExtensibleWithBackButton import com.vitorpamplona.amethyst.ui.navigation.TopBarExtensibleWithBackButton
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
@@ -584,6 +586,8 @@ fun PrivateMessageEditFieldRow(
galleryUri = it, galleryUri = it,
alt = null, alt = null,
sensitiveContent = false, sensitiveContent = false,
// use MEDIUM quality
mediaQuality = MediaCompressor().compressorQualityToInt(CompressorQuality.MEDIUM),
isPrivate = isPrivate, isPrivate = isPrivate,
server = ServerOption(accountViewModel.account.settings.defaultFileServer, false), server = ServerOption(accountViewModel.account.settings.defaultFileServer, false),
onError = accountViewModel::toast, onError = accountViewModel::toast,
@@ -817,4 +817,10 @@
<string name="torrent_download">Stáhnout</string> <string name="torrent_download">Stáhnout</string>
<string name="torrent_failure">Nepodařilo se otevřít soubor</string> <string name="torrent_failure">Nepodařilo se otevřít soubor</string>
<string name="torrent_no_apps">Pro otevření a stažení souboru nejsou nainstalovány žádné torrent aplikace.</string> <string name="torrent_no_apps">Pro otevření a stažení souboru nejsou nainstalovány žádné torrent aplikace.</string>
<string name="media_compression_quality_label">Kvalita médií</string>
<string name="media_compression_quality_explainer">Vyberte Nízkou kvalitu pro kompresi médií na menší soubor s nižší kvalitou, nebo vyberte Vysokou kvalitu pro kompresi na větší soubor s vyšší kvalitou.</string>
<string name="media_compression_quality_low">Nízká</string>
<string name="media_compression_quality_medium">Střední</string>
<string name="media_compression_quality_high">Vysoká</string>
</resources> </resources>
@@ -822,4 +822,9 @@ anz der Bedingungen ist erforderlich</string>
<string name="torrent_download">Herunterladen</string> <string name="torrent_download">Herunterladen</string>
<string name="torrent_failure">Fehler beim Öffnen der Datei</string> <string name="torrent_failure">Fehler beim Öffnen der Datei</string>
<string name="torrent_no_apps">Keine Torrent-Apps installiert, um die Datei zu öffnen und herunterzuladen.</string> <string name="torrent_no_apps">Keine Torrent-Apps installiert, um die Datei zu öffnen und herunterzuladen.</string>
<string name="media_compression_quality_label">Medienqualität</string>
<string name="media_compression_quality_explainer">Wählen Sie Niedrige Qualität, um Ihre Medien in eine kleinere Datei mit geringerer Qualität zu komprimieren, oder wählen Sie Hohe Qualität, um sie in eine größere Datei mit höherer Qualität zu komprimieren.</string>
<string name="media_compression_quality_low">Niedrig</string>
<string name="media_compression_quality_medium">Mittel</string>
<string name="media_compression_quality_high">Hoch</string>
</resources> </resources>
@@ -817,4 +817,9 @@
<string name="torrent_download">Baixar</string> <string name="torrent_download">Baixar</string>
<string name="torrent_failure">Falha ao abrir o arquivo</string> <string name="torrent_failure">Falha ao abrir o arquivo</string>
<string name="torrent_no_apps">Nenhum aplicativo torrent instalado para abrir e baixar o arquivo.</string> <string name="torrent_no_apps">Nenhum aplicativo torrent instalado para abrir e baixar o arquivo.</string>
<string name="media_compression_quality_label">Qualidade de Mídia</string>
<string name="media_compression_quality_explainer">Selecione Baixa qualidade para comprimir sua mídia para um arquivo menor com menor qualidade ou selecione Alta qualidade para comprimir para um arquivo maior com maior qualidade.</string>
<string name="media_compression_quality_low">Baixa</string>
<string name="media_compression_quality_medium">Média</string>
<string name="media_compression_quality_high">Alta</string>
</resources> </resources>
@@ -816,4 +816,10 @@
<string name="torrent_download">Ladda ner</string> <string name="torrent_download">Ladda ner</string>
<string name="torrent_failure">Det gick inte att öppna filen</string> <string name="torrent_failure">Det gick inte att öppna filen</string>
<string name="torrent_no_apps">Inga torrent-appar installerade för att öppna och ladda ner filen.</string> <string name="torrent_no_apps">Inga torrent-appar installerade för att öppna och ladda ner filen.</string>
<string name="media_compression_quality_label">Mediakvalitet</string>
<string name="media_compression_quality_explainer">Välj Låg kvalitet för att komprimera ditt media till en mindre fil med lägre kvalitet, eller välj Hög kvalitet för att komprimera till en större fil med högre kvalitet.</string>
<string name="media_compression_quality_low">Låg</string>
<string name="media_compression_quality_medium">Medel</string>
<string name="media_compression_quality_high">Hög</string>
</resources> </resources>
+6
View File
@@ -791,6 +791,12 @@
<string name="compression_cancelled">Compression Cancelled</string> <string name="compression_cancelled">Compression Cancelled</string>
<string name="compression_returned_null">Compression failed to return a file</string> <string name="compression_returned_null">Compression failed to return a file</string>
<string name="media_compression_quality_label">Media Quality</string>
<string name="media_compression_quality_explainer">Select Low quality to compress your media to a smaller file with less quality or select High quality to compress to a larger file with higher quality.</string>
<string name="media_compression_quality_low">Low</string>
<string name="media_compression_quality_medium">Medium</string>
<string name="media_compression_quality_high">High</string>
<string name="edit_draft">Edit draft</string> <string name="edit_draft">Edit draft</string>
<string name="login_with_qr_code">Login with QR Code</string> <string name="login_with_qr_code">Login with QR Code</string>