本文主要讨论如下三个问题:
先定义媒体信息数据结构MediaInfo,以及视频信息数据结构VideoInfo。
open class MediaInfo(
var size: Long = 0L, // 大小 单位B
var width: Float = 0f, // 宽
var height: Float = 0f, // 高
var localPath: String = "", // 系统绝对路径
var localPathUri: String = "", // 媒体文件Uri
var fileName: String = "", // 文件名
var mimeType: String = "", // 媒体类型
var mediaId: String = "", // 媒体ID
var lastModified: Long = 0L, // 最后更改时间
/*----业务字段----*/
var seq: Int = 0, // 事件序列号
/*----业务字段----*/
)
/**
* 码率(比特率),单位为 bps,比特率越高,传送的数据速度越快。在压缩视频时指定码率,则可确定压缩后的视频大小。
* 视频大小(byte) = (duration(ms) / 1000) * (biteRate(bit/s) / 8)
*/
data class VideoInfo(
var firstFrame: Bitmap? = null, // 视频第一帧图
var duration: Long = 0L, // 视频长度 ms
var biteRate: Long = 0L, // 视频码率 bps
/* --------not necessary, maybe not value---- */
var addTime: Long = 0L, // 视频添加时间
var videoRotation: Int = 0, // 视频方向
/* --------not necessary, maybe not value---- */
) : MediaInfo()
这里直接去Android媒体库找到所有视频即可,代码使用了Kotlin协程,参考如下。
import android.provider.MediaStore.Video.Media
/**
* 获取系统所有视频文件
*/
suspend fun getSystemVideos(contentResolver: ContentResolver): MutableList<MediaInfo> =
withContext(Dispatchers.IO) {
val videoList: MutableList<MediaInfo> = mutableListOf()
var cursor: Cursor? = null
try {
val queryArray = arrayOf(
Media._ID,
Media.SIZE, // 视频大小
Media.WIDTH, // 视频宽
Media.HEIGHT, // 视频高
Media.DATA, // 视频绝对路径
Media.DISPLAY_NAME, // 视频文件名
Media.MIME_TYPE, // 媒体类型
Media.DURATION, // 视频长度
Media.DATE_ADDED, // 视频添加时间
Media.DATE_MODIFIED, // 视频最后更改时间
).toMutableList()
if (SDK_INT >= Build.VERSION_CODES.R) queryArray.add(Media.BITRATE)// 视频码率
cursor = contentResolver.query(
Media.EXTERNAL_CONTENT_URI, queryArray.toTypedArray(),
null, null, Media.DATE_ADDED + " DESC", null
)
cursor?.moveToFirst()
if (cursor == null || cursor.isAfterLast) return@withContext videoList
while (!cursor.isAfterLast) {
getVideoInfo(cursor).run { if (duration > 0 && size > 0) videoList.add(this) }
cursor.moveToNext()
}
} catch (e: Exception) {
} finally {
cursor?.close()
}
videoList
}
注:getVideoInfo获取视频文件信息可参考附件1。
可以通过视频系统路径,直接使用getFrameAtTime方法拿到第一帧作为缩略图。
val retriever = MediaMetadataRetriever()
retriever.setDataSource("filePath")
val bitmap: Bitmap? = retriever.frameAtTime
也可以使用getScaledFrameAtTime,指定缩略图Bitmap尺寸512*512。
retriever.getScaledFrameAtTime(-1, OPTION_CLOSEST_SYNC,512,512)
ThumbnailUtils.createVideoThumbnail("filePath",MediaStore.Video.Thumbnails.FULL_SCREEN_KIND)
ThumbnailUtils.createVideoThumbnail其实也是使用的MediaMetadataRetriever,如下源码截图:


Glide.with(mContext)
.load(Uri.fromFile(File("filePath")))
.into(binding.icon)
项目中一般会使用图片加载框架如Glide,它内部也是支持加载视频作为图片的,亦是使用的MediaMetadataRetriever。


直接从Android媒体库中查询缩略图,但是调试时发现未找到(视频为手机本地录制mp4格式) ,下面是查询视频缩略图的方法:
/**
* 获取视频缩略图:从媒体库中查询
*/
@Deprecated("暂时不可用", replaceWith = ReplaceWith("getVideoThumbnail"))
suspend fun getVideoThumbnailDefault(
contentResolver: ContentResolver,
cursor: Cursor
): Bitmap? = withContext(Dispatchers.IO) {
// // 方式一:能拿到路径"content://media/external/images/media/126",但没有具体图片
// BitmapFactory.decodeFile(
// ContentUris.withAppendedId(
// MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
// cursor.getLong(cursor.getColumnIndex(Media._ID))
// ).toString()
// )
//
// // 方式二:拿到的是video视频路径"/storage/emulated/0/DCIM/Camera/20221122_200648.mp4"
// BitmapFactory.decodeFile(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)))
// 方式三:开发调试时发现,thumbCursor.moveToFirst()为false,也就是说cursor为空,视频缩略图路径未找到
contentResolver.query(
Thumbnails.EXTERNAL_CONTENT_URI,
arrayOf(Thumbnails.DATA, Thumbnails.VIDEO_ID),
Thumbnails.VIDEO_ID + "=" + cursor.getInt(cursor.getColumnIndex(Media._ID)),
null,
null
)?.let { thumbCursor ->
if (thumbCursor.moveToFirst()) {
// 获取视频缩略图路径,并转为bitmap
// MediaStore.Video.Thumbnails.DATA: 视频缩略图的文件路径
BitmapFactory.decodeFile(thumbCursor.getString(thumbCursor.getColumnIndex(Thumbnails.DATA)))
} else null
}
}
Android 10对文件访问更加严格,不能直接操作视频绝对路径,如要操作则须加上在AndroidManifest.xml的application标签下声明requestLegacyExternalStorage=true,才可以访问非沙盒路径下的数据。
但Google Play发布的应用,不允许带有requestLegacyExternalStorage,所以对于Android 10不能采用操作视频的绝对路径,改为操作视频的Uri即可。
1) 针对Android 10可以采用更高版本API获取视频第一帧,如下:
/**
* 获取视频缩略图:兼容Android 10
*/
suspend fun getVideoThumbnail(context: Context, uriString: String): Bitmap? =
withContext(Dispatchers.IO) {
var bitmap: Bitmap? = null
if (SDK_INT >= Build.VERSION_CODES.Q) {
try {
bitmap = context.contentResolver.loadThumbnail(
Uri.parse(uriString), Size(
THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt(),
THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt()
), null
)
} catch (e: Exception) {
}
} else getVideoThumbnail(uriString)
bitmap
}
2)针对Android 10,也可以使用视频Uri去获取第一帧,如下:
suspend fun getVideoThumbnail(context: Context, uriString: String): Bitmap? =
withContext(Dispatchers.IO) {
var bitmap: Bitmap? = null
val retriever = MediaMetadataRetriever()
try {
retriever.setDataSource(context, Uri.parse(uriString))
// OPTION_CLOSEST_SYNC:在给定的时间,检索最近一个同步与数据源相关联的的帧(关键帧)
// OPTION_CLOSEST:表示获取离该时间戳最近帧(I帧或P帧)
bitmap = if (SDK_INT >= Build.VERSION_CODES.O_MR1) {
retriever.getScaledFrameAtTime(
VIDEO_FIRST_FRAME_TIME_US, OPTION_CLOSEST_SYNC,
THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt(),
THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt()
)
} else {
retriever.getFrameAtTime(VIDEO_FIRST_FRAME_TIME_US)
?.let { compressVideoThumbnail(it) }
}
} catch (e: Exception) {
} finally {
try {
retriever.release()
} catch (e: Exception) {
}
}
bitmap
}
3)但,Android 10,不能使用视频绝对路径去获取第一帧,会存在权限不足问题,如下:
/**
* 获取视频缩略图:从路径中拿取第一帧,Android 10不可用
*/
suspend fun getVideoThumbnail(filePath: String): Bitmap? = withContext(Dispatchers.IO) {
var bitmap: Bitmap? = null
val retriever = MediaMetadataRetriever()
try {
retriever.setDataSource(filePath)
// OPTION_CLOSEST_SYNC:在给定的时间,检索最近一个同步与数据源相关联的的帧(关键帧)
// OPTION_CLOSEST:表示获取离该时间戳最近帧(I帧或P帧)
bitmap = if (SDK_INT >= Build.VERSION_CODES.O_MR1) {
retriever.getScaledFrameAtTime(
VIDEO_FIRST_FRAME_TIME_US, OPTION_CLOSEST_SYNC,
THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt(),
THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt()
)
} else {
retriever.getFrameAtTime(VIDEO_FIRST_FRAME_TIME_US)
?.let { compressVideoThumbnail(it) }
}
} catch (e: Exception) {
} finally {
try {
retriever.release()
} catch (e: Exception) {
}
}
bitmap
}
综上,使用第二种方式是一种比较统一的方式,不存在版本兼容问题。
参考:
https://blog.csdn.net/g984160547/article/details/115749804
android 10(Q)上传图片及视频,访问媒体文件适配_李小白的生活的博客-CSDN博客_android 10 上传文件
Android媒体库可以获取缩略图但不稳定;
第三方图片库如Glide以及ThumbnailUtils都是采用MediaMetadataRetriever。
所以根本上来说,目前有两种方式拿到视频缩略图,Android媒体库或MediaMetadataRetriever,一般来说采用MediaMetadataRetriever方式,参考代码如下:
const val VIDEO_FIRST_FRAME_TIME_US = 1000L
/**
* 视频缩略图默认压缩尺寸
*/
const val THUMBNAIL_DEFAULT_COMPRESS_VALUE = 1024f
/**
* 获取视频缩略图:通过绝对路径抓取第一帧
* @param filePath 视频在系统绝对路径。如:/storage/emulated/0/Pictures/Screenshots/SVID_20221109_181203_1.mp4
*/
suspend fun getVideoThumbnail(filePath: String): Bitmap? = withContext(Dispatchers.IO) {
var bitmap: Bitmap? = null
val retriever = MediaMetadataRetriever()
try {
retriever.setDataSource(filePath)
// OPTION_CLOSEST_SYNC:在给定的时间,检索最近一个同步与数据源相关联的的帧(关键帧)
// OPTION_CLOSEST:表示获取离该时间戳最近帧(I帧或P帧)
bitmap = if (SDK_INT >= Build.VERSION_CODES.O_MR1) {
retriever.getScaledFrameAtTime(
VIDEO_FIRST_FRAME_TIME_US, OPTION_CLOSEST_SYNC,
THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt(),
THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt()
)
} else {
retriever.getFrameAtTime(VIDEO_FIRST_FRAME_TIME_US)
?.let { compressVideoThumbnail(it) }
}
} catch (e: Exception) {
} finally {
try {
retriever.release()
} catch (e: Exception) {
}
}
bitmap
}
/**
* 获取视频缩略图:通过Uri抓取第一帧
* @param videoUriString 视频在媒体库中Uri。如:content://media/external/video/media/11378
*/
suspend fun getVideoThumbnail(context: Context, videoUriString: String): Bitmap? =
withContext(Dispatchers.IO) {
var bitmap: Bitmap? = null
val retriever = MediaMetadataRetriever()
try {
retriever.setDataSource(context, Uri.parse(videoUriString))
// OPTION_CLOSEST_SYNC:在给定的时间,检索最近一个同步与数据源相关联的的帧(关键帧)
// OPTION_CLOSEST:表示获取离该时间戳最近帧(I帧或P帧)
bitmap = if (SDK_INT >= Build.VERSION_CODES.O_MR1) {
retriever.getScaledFrameAtTime(
VIDEO_FIRST_FRAME_TIME_US, OPTION_CLOSEST_SYNC,
THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt(),
THUMBNAIL_DEFAULT_COMPRESS_VALUE.toInt()
)
} else {
retriever.getFrameAtTime(VIDEO_FIRST_FRAME_TIME_US)
?.let { compressVideoThumbnail(it) }
}
} catch (e: Exception) {
} finally {
try {
retriever.release()
} catch (e: Exception) {
}
}
bitmap
}
为避免应用OOM,需要对缩略图大小进行压缩compressVideoThumbnail,下面看看Bitmap压缩方式。
宽高压缩、缩放法压缩可针对Bitmap操作,而采样率压缩和质量压缩针对于File、Resource操作,下面可主要看看宽高压缩、缩放法压缩。
/**
* 视频缩略图默认压缩尺寸
*/
const val THUMBNAIL_DEFAULT_COMPRESS_VALUE = 512f
/**
* 压缩视频缩略图
* @param bitmap 视频缩略图
*/
fun compressVideoThumbnail(bitmap: Bitmap): Bitmap? {
val width: Int = bitmap.width
val height: Int = bitmap.height
val max: Int = Math.max(width, height)
if (max > THUMBNAIL_DEFAULT_COMPRESS_VALUE) {
val scale: Float = THUMBNAIL_DEFAULT_COMPRESS_VALUE / max
val w = (scale * width).roundToInt()
val h = (scale * height).roundToInt()
return compressVideoThumbnail(bitmap, w, h)
}
return bitmap
}
/**
* 压缩视频缩略图:宽高压缩
* 注:如果用户期望的长度和宽度和原图长度宽度相差太多的话,图片会很不清晰。
* @param bitmap 视频缩略图
*/
fun compressVideoThumbnail(bitmap: Bitmap, width: Int, height: Int): Bitmap? {
return Bitmap.createScaledBitmap(bitmap, width, height, true)
}
/**
* 视频缩略图默认压缩比例
*/
private const val THUMBNAIL_DEFAULT_SCALE_VALUE = 0.5f
/**
* 压缩视频缩略图:缩放法压缩
* 注:长度和宽度没有变,内存缩小4倍(宽高各缩小一半)
*/
fun compressVideoThumbnailMatrix(bitmap: Bitmap): Bitmap? {
val matrix = Matrix()
matrix.setScale(THUMBNAIL_DEFAULT_SCALE_VALUE, THUMBNAIL_DEFAULT_SCALE_VALUE)
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
}
其实,宽高压缩调用createScaledBitmap方法,最终也是走的缩放法压缩Matrix.setScale,如下图源码,通过宽高计算出scale值,进而进行压缩:

/**
* 压缩视频缩略图:采样率压缩
* @param filePath 视频缩略图路径
*/
fun compressVideoThumbnailSample(filePath: String, width: Int, height: Int): Bitmap? {
return BitmapFactory.Options().run {
// inJustDecodeBounds 设置为 true后,BitmapFactory.decodeFile 不生成Bitmap对象,而仅仅是读取该图片的尺寸和类型信息。
inJustDecodeBounds = true
BitmapFactory.decodeFile(filePath, this)
inSampleSize = calculateInSampleSize(this, width, height)
inJustDecodeBounds = false
BitmapFactory.decodeFile(filePath, this)
}
}
计算采样率,可有以下2种方式:
/**
* 计算采样率:值为2的幂。例如, 一个分辨率为2048x1536的图片,如果设置 inSampleSize 为4,那么会产出一个大约512x384大小的Bitmap。
* @param options
* @param reqWidth 想要压缩到的宽度
* @param reqHeight 想要压缩到的高度
* @return
*/
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
var inSampleSize = 1
if (options.outHeight > reqHeight || options.outWidth > reqWidth) {
val halfHeight: Int = options.outHeight / 2
val halfWidth: Int = options.outWidth / 2
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}
/**
* 计算采样率:值为整数的采样率。
*/
fun calculateInSampleSizeFixed(options: BitmapFactory.Options, width: Int, height: Int): Int {
val hRatio = ceil(options.outHeight.div(height.toDouble())) // 大于1:图片高度>手机屏幕高度
val wRatio = ceil(options.outWidth.div(width.toDouble())) // 大于1:图片宽度>手机屏幕宽度
return if (hRatio > wRatio) hRatio.toInt() else wRatio.toInt()
}
其实,这里的采样率压缩称之为采用邻近点插值算法的邻近采样,即:如果采样率设置为2,意味着直接选择其中的一个像素作为生成像素,另一个像素直接抛弃。
另外,宽高压缩或缩放法压缩称为双线性采样,且:它不像邻近点插值算法一样,直接粗暴的选择一个像素,而是参考了源像素相应位置周围 2x2 个点的值,根据相对位置取对应的权重,经过计算之后得到目标图像。
双线性采样对比邻近采样的优势在于:
因为,质量压缩会让图片在磁盘中的大小(File文件的大小)减小,但是图片的像素数不会改变,加载压缩后的图片,占据的内存不会减少,所以我们这里内存中视频缩略图的压缩,就先不展开讨论此种方式。具体实现可参考此文 。
注:
图片压缩方法根据实际情况去选择即可,比如微信图片压缩算法,大致就是按照以下步骤进行的:
【附1:获取视频文件信息】
/**
* 获取视频文件信息
* 注:(1)暂未包括videoRotation;(2)biteRate通过文件大小和视频时长计算
*/
suspend fun getVideoInfo(cursor: Cursor): VideoInfo = withContext(Dispatchers.IO) {
val videoInfo = VideoInfo()
videoInfo.mediaId = cursor.getString(cursor.getColumnIndex(Media._ID))
videoInfo.size = cursor.getLong(cursor.getColumnIndex(Media.SIZE))
videoInfo.width = cursor.getFloat(cursor.getColumnIndex(Media.WIDTH))
videoInfo.height = cursor.getFloat(cursor.getColumnIndex(Media.HEIGHT))
videoInfo.localPath = cursor.getString(cursor.getColumnIndex(Media.DATA))
videoInfo.localPathUri = getVideoPathUri(videoInfo.mediaId).toString()
videoInfo.fileName = cursor.getString(cursor.getColumnIndex(Media.DISPLAY_NAME))
videoInfo.mimeType = cursor.getString(cursor.getColumnIndex(Media.MIME_TYPE))
// 不能在这获取第一帧,太耗时,改为使用的地方去获取第一帧。
// videoInfo.firstFrame = getVideoThumbnail(cursor.getString(cursor.getColumnIndex(Media.DATA)))
videoInfo.duration = cursor.getLong(cursor.getColumnIndex(Media.DURATION))
videoInfo.biteRate =
if (SDK_INT >= Build.VERSION_CODES.R) cursor.getLong(cursor.getColumnIndex(Media.BITRATE))
else ((8 * videoInfo.size * 1024) / (videoInfo.duration / 1000f)).toLong()
videoInfo.addTime = cursor.getLong(cursor.getColumnIndex(Media.DATE_ADDED))
videoInfo.lastModified = cursor.getLong(cursor.getColumnIndex(Media.DATE_MODIFIED))
videoInfo
}
【附2:通过视频路径直接获取文件信息】
/**
* 获取视频文件信息
* 注:(1)暂未包括lastModified、addTime;(2)mediaId以filePath代替
*
* @param path 视频文件的路径
* @return VideoInfo 视频文件信息
*/
suspend fun getVideoInfo(path: String?): VideoInfo = withContext(Dispatchers.IO) {
val videoInfo = VideoInfo()
if (!path.isNullOrEmpty()) {
val media = MediaMetadataRetriever()
try {
media.setDataSource(path)
videoInfo.size =
File(path).let { if (FileUtils.isFileExists(it)) it.length() else 0 }
videoInfo.width = media.extractMetadata(METADATA_KEY_VIDEO_WIDTH)?.toFloat() ?: 0f
videoInfo.height = media.extractMetadata(METADATA_KEY_VIDEO_HEIGHT)?.toFloat() ?: 0f
videoInfo.localPath = path
videoInfo.localPathUri =
getVideoPathUri(ApplicationUtils.getApplication(), path).toString()
videoInfo.fileName = path.split(File.separator).let {
if (it.isNotEmpty()) it[it.size - 1] else ""
}
videoInfo.mimeType = media.extractMetadata(METADATA_KEY_MIMETYPE) ?: ""
videoInfo.firstFrame =
media.getFrameAtTime(VIDEO_FIRST_FRAME_TIME_US)
?.let { compressVideoThumbnail(it) }
videoInfo.duration = media.extractMetadata(METADATA_KEY_DURATION)?.toLong() ?: 0
videoInfo.biteRate = media.extractMetadata(METADATA_KEY_BITRATE)?.toLong() ?: 0
videoInfo.videoRotation =
media.extractMetadata(METADATA_KEY_VIDEO_ROTATION)?.toInt() ?: 0
videoInfo.mediaId = path
} catch (e: Exception) {
} finally {
media.release()
}
}
videoInfo
}
【附3:获取视频Uri】
private const val URI_VIDEO_PRE = "content://media/external/video/media"
/**
* 通过视频资源ID,直接获取视频Uri
* @param mediaId 视频资源ID
*/
fun getVideoPathUri(mediaId: String): Uri =
Uri.withAppendedPath(Uri.parse(URI_VIDEO_PRE), mediaId)
/**
* 通过视频资源本地路径,获取视频Uri
* @param path 视频资源本地路径
*/
fun getVideoPathUri(context: Context, path: String): Uri? {
var uri: Uri? = null
var cursor: Cursor? = null
try {
cursor = context.contentResolver.query(
Media.EXTERNAL_CONTENT_URI, arrayOf(Media._ID),
Media.DATA + "=? ", arrayOf(path), null
)
uri = if (cursor != null && cursor.moveToFirst()) {
Uri.withAppendedPath(
Uri.parse(URI_VIDEO_PRE),
"" + cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID))
)
} else {
if (File(path).exists()) {
val values = ContentValues()
values.put(Media.DATA, path)
context.contentResolver.insert(Media.EXTERNAL_CONTENT_URI, values)
} else {
null
}
}
} catch (e: Exception) {
} finally {
cursor?.close()
}
return uri
}
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI
动漫制作技巧是很多新人想了解的问题,今天小编就来解答与大家分享一下动漫制作流程,为了帮助有兴趣的同学理解,大多数人会选择动漫培训机构,那么今天小编就带大家来看看动漫制作要掌握哪些技巧?一、动漫作品首先完成草图设计和原型制作。设计草图要有目的、有对象、有步骤、要形象、要简单、符合实际。设计图要一致性,以保证制作的顺利进行。二、原型制作是根据设计图纸和制作材料,可以是手绘也可以是3d软件创建。在此步骤中,要注意的问题是色彩和平面布局。三、动漫制作制作完成后,加工成型。完成不同的表现形式后,就要对设计稿进行加工处理,使加工的难易度降低,并得到一些基本准确的概念,以便于后续的大样、准确的尺寸制定。四、
2022/8/4更新支持加入水印水印必须包含透明图像,并且水印图像大小要等于原图像的大小pythonconvert_image_to_video.py-f30-mwatermark.pngim_dirout.mkv2022/6/21更新让命令行参数更加易用新的命令行使用方法pythonconvert_image_to_video.py-f30im_dirout.mkvFFMPEG命令行转换一组JPG图像到视频时,是将这组图像视为MJPG流。我需要转换一组PNG图像到视频,FFMPEG就不认了。pyav内置了ffmpeg库,不需要系统带有ffmpeg工具因此我使用ffmpeg的python包装p
Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图
最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路
我的Rails站点使用了一个确实不是很好的gem。每次我需要做一些新的事情时,我最终不得不花费与向实际Rails项目添加代码一样多的时间来为gem添加功能。但我不介意,我将我的Gemfile设置为指向我的gem的GitHub分支(我尝试提交PR,但维护者似乎已经下台)。问题是我真的没有找到一种合理的方法来测试我添加到gem的新东西。在railsc中测试它会特别好,但我能想到的唯一方法是a)更改~/.rvm/gems/.../foo。rb,这看起来不对或者b)升级版本,推送到Github,然后运行bundleup,这除了耗时之外显然是一场灾难,因为我不确定我所做的promise是否正
我希望访问我机器上的所有HTTP流量(我的Windows机器-不是服务器)。据我了解,拥有一个本地代理是所有流量路线的必经之路。我一直在谷歌搜索但未能找到任何资源(关于Ruby)来帮助我。非常感谢任何提示或链接。 最佳答案 WEBrick中有一个HTTP代理(Rubystdlib的一部分)和here's一个实现示例。如果你喜欢生活在边缘,还有em-proxy伊利亚·格里戈里克。这postIlya暗示它似乎确实需要一些调整来解决您的问题。 关于ruby-如何捕获所有HTTP流量(本地代理)
我正在尝试找出一种方法来显示来自不在RAILS_ROOT下(在RedHat或Ubuntu环境中)的已安装文件系统的图像。我不想使用符号链接(symboliclink),因为这个应用程序实际上是通过Tomcat部署的,而当我关闭Tomcat时,Tomcat会尝试跟随符号链接(symboliclink)并删除挂载中的所有图像。由于这些文件的数量和大小,将图像放在public/images下也不是一种选择。我查看了send_file,但它只会显示一张图片。我需要在一个格式良好的页面中显示6个请求的图像。由于膨胀,我宁愿不使用Base64编码,但我不知道如何将图像数据与呈现的页面一起传递下去。
我刚刚在我的Ubuntu9.10服务器上安装了TeamBox。我使用提供的服务器脚本在端口3000上启动并运行它。它的运行速度非常慢,从另一台计算机连接时每个HTTP请求最多需要30秒。我使用链接从shell加载TeamBox,一点也不花时间。然后我设置了一个SSH隧道,它再次运行得非常快。我通过此服务器上的apache以及SAMBA等运行了大约30个虚拟主机,没有任何问题。我该如何解决这个问题? 最佳答案 我的redmine(ruby,webrick)太慢了。现在我解决了这个问题:apt-getinstallmongrelruby