前言

         在上两篇文章中,我们分别实现了通过OpenGL预览、录制视频,以及在预览和录制的时候加上视频水印和美白效果,而作为一个视频编辑器,当然不仅仅是录制视频,也会有从本地选择视频,然后加上视频水印、美白、滤镜等效果,再进行发布的需求。所以作为系列文章的第三篇,我们就来实现如何给本地视频加上视频水印和美颜效果。
       本系列的文章包括如下:

1、android视频编辑器之视频录制、断点续录、对焦等
2、android视频编辑器之录制过程中加水印和美白效果
3、android视频编辑器之本地视频加美白效果和加视频水印
4、android视频编辑器之通过OpenGL给视频加各类滤镜
5、android视频编辑器之音频编解码、mono转stereo、音频混音、音频音量调节
6、android视频编辑器之通过OpenGL做本地视频拼接
7、android视频编辑器之音视频裁剪、增加背景音乐等

        要实现给本地视频加上视频水印和美颜效果,肯定离不开视频编解码和OpenGL,所以本篇文章会涉及到一些视频编解码的知识。当然是纯Android平台的视频编解码,还是不会涉及到FFmpeg。在上面的文章中,我们实现利用OpenGL处理视频的主要思路是,从Camera获取到数据,然后把每一帧的数据给到OpenGL,然后利用OpenGL的图像处理技术,对每一帧的画面进行处理之后,再将视频数据反馈给我们,我们再通过SurfaceView显示在屏幕上或者录制成视频文件。
       而这篇文章我们要实现对本地已有视频加上美颜和水印效果,有两个比较核心的点:
       第一个是如何实现预览本地视频的时候加水印和美颜效果
       第二个是如何解码视频,加上水印和美颜效果,再进行编码视频,保存到本地。
       所以本篇博客,我们就会从这个部分来,一一讲解,并实现功能

预览本地视频的时候加上水印和美白效果

      当然,我们肯定首先要实现预览本地视频,然后让用户看有无滤镜的区别,所以我们先来实现本地视频的预览。
       Android端播放本地视频,我们可以用MediaPlayer+GLSurfaceView简单实现(因为我们不考虑多种音视频格式和android低版本的兼容),而MediaPlayer可以设置一个Surface,而我们就可以通过设置这个Surface将其和OpenGL的SurfaceTexture联系起来,也就是又和我们从摄像头获取数据类似了,我们通过一个VideoDrawer来控制本地视频的OpenGL绘制。在ViewDrawer中来加上各种滤镜,从而实现对原视频数据进行修改的功能
      首先跟上文中的预览摄像头数据一样,我们需要先自定义一个播放视频的View,因为要用到OpenGL,所以该View同样是继承自GLSurfaceView。
   public class VideoPreviewView extends GLSurfaceView{}

然后在初始化函数中,设置进行OpenGL初始化

   private void init(Context context) {setEGLContextClientVersion(2);setRenderer(this);setRenderMode(RENDERMODE_WHEN_DIRTY);setPreserveEGLContextOnPause(false);setCameraDistance(100);mDrawer = new VideoDrawer(context,getResources());//初始化Drawer和VideoPlayermMediaPlayer = new MediaPlayerWrapper();mMediaPlayer.setOnCompletionListener(this);}

上面代码中的VideoDrawer和MediaPlayerWrapper就是控制OpenGL绘制和视频播放的重点类,其实VideoPreviewView类和我们之前的CameraView类是完全类似的,只不过一个是从摄像头获取数据,一个是从视频解码器获取数据而已。下面我们来分别说说mediaPlayerWrapper和VideoDrawer类。

VideoDrawer类
      该类,其实和我们之前文章中说过的CameraDrawer类,可以说基本上是一致的,首先实现GLSurfaceView.Renderer接口,当然其实你也可以自定义接口,因为主要通过该接口的三个函数进行过程控制,而这三个函数其实都是在我们上面的VideoPreviewView里面自己进行调用的,出于命名的容易理解,所以还是直接用Renderer接口了。

    public class VideoDrawer implements GLSurfaceView.Renderer {}

然后,在构造函数中初始化要用到的Filter,包括美颜的MagicBeautyFilter和加水印的WaterMarkFilter

    public VideoDrawer(Context context,Resources res){mPreFilter = new RotationOESFilter(res);//旋转相机操作mShow = new NoFilter(res);mBeFilter = new GroupFilter(res);mBeautyFilter = new MagicBeautyFilter();mProcessFilter=new ProcessFilter(res);      WaterMarkFilter waterMarkFilter = new WaterMarkFilter(res);waterMarkFilter.setWaterMark(BitmapFactory.decodeResource(res, R.mipmap.watermark));       waterMarkFilter.setPosition(0,70,0,0);mBeFilter.addFilter(waterMarkFilter);}

上面这段代码就不进行过多解释了,在该系列文章的第二篇中,有比较详细的解释,包括美白Filter和水印Filter的实现原理,不懂的童鞋,请翻阅上篇文章。

      
     然后同样是在onSurfaceCreated中进行纹理的创建和滤镜的初始化
    @Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {int texture[]=new int[1];GLES20.glGenTextures(1,texture,0);GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES ,texture[0]);GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);surfaceTexture = new SurfaceTexture(texture[0]);mPreFilter.create();mPreFilter.setTextureId(texture[0]);mBeFilter.create();mProcessFilter.create();mShow.create();mBeautyFilter.init();mBeautyFilter.setBeautyLevel(3);//默认设置3级的美颜}

在onSurfaceChanged函数中,设置视图、纹理、滤镜的宽高

    @Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {viewWidth=width;viewHeight=height;GLES20.glDeleteFramebuffers(1, fFrame, 0);GLES20.glDeleteTextures(1, fTexture, 0);GLES20.glGenFramebuffers(1,fFrame,0);EasyGlUtils.genTexturesWithParameter(1,fTexture,0, GLES20.GL_RGBA,viewWidth,viewHeight);mBeFilter.setSize(viewWidth,viewHeight);mProcessFilter.setSize(viewWidth,viewHeight);mBeautyFilter.onDisplaySizeChanged(viewWidth,viewHeight);mBeautyFilter.onInputSizeChanged(viewWidth,viewHeight);}

然后在onDrawFrame中,对每一帧的视频数据进行处理,并且显示

    @Overridepublic void onDrawFrame(GL10 gl) {surfaceTexture.updateTexImage();EasyGlUtils.bindFrameTexture(fFrame[0],fTexture[0]);GLES20.glViewport(0,0,viewWidth,viewHeight);mPreFilter.draw();EasyGlUtils.unBindFrameBuffer();mBeFilter.setTextureId(fTexture[0]);mBeFilter.draw();if (mBeautyFilter != null && isBeauty && mBeautyFilter.getBeautyLevel() != 0){EasyGlUtils.bindFrameTexture(fFrame[0],fTexture[0]);GLES20.glViewport(0,0,viewWidth,viewHeight);mBeautyFilter.onDrawFrame(mBeFilter.getOutputTexture());EasyGlUtils.unBindFrameBuffer();mProcessFilter.setTextureId(fTexture[0]);}else {mProcessFilter.setTextureId(mBeFilter.getOutputTexture());}mProcessFilter.draw();GLES20.glViewport(0,0,viewWidth,viewHeight);mShow.setTextureId(mProcessFilter.getOutputTexture());mShow.draw();}
这段的代码其实很清晰,首先绑定Frame缓冲区和texture,然后mPreFilter进行预先绘制,通过mBeFilter绘制视频水印,如果当前是开启了美颜的话(通过isBeauty进行判断)就通过mBeautyFilter滤镜进行美颜效果的绘制,然后通过mShow进行显示绘制

然后就是对外提供的美颜效果的开关

    /**切换开启美白效果*/public void switchBeauty(){isBeauty = !isBeauty;}

VideoDrawer类基本上就是这些,跟CameraDrawer的添加水印和美白效果的方式完全一样,但是少了视频录制的相关过程,因为我们对本地视频的处理并不是实时录制的,而是后面才进行录制,所以其实更加简单了。

MediaPlayerWrapper类
       然后就是MediaPlayerWrapper类,该类其实是MediaPlayer这个系统类的一个包装类,播放视频本质上使用的还是MediaPlayer类,不过我们在该类的基础上添加了一些新的功能。为了功能的扩展,其实该类的主要变化是,提供了无缝播放多个视频的功能,利用List进行多个MediaPlayer的保存,无缝切换视频播放。不熟悉MediaPlayer的请查阅相关资料。
       我们在构造函数中,初始化了两个ArrayList,用于保存多个Player和VideoInfo

    private List<MediaPlayer> mPlayerList;  //player listprivate List<String> mSrcList;          //video src listprivate List<VideoInfo> mInfoList;      //video info listpublic MediaPlayerWrapper() {mPlayerList = new ArrayList<>();mInfoList = new ArrayList<>();}

提供了setDataSource方法,用于设置视频的播放地址

   public void setDataSource(List<String> dataSource) {this.mSrcList = dataSource;MediaMetadataRetriever retr = new MediaMetadataRetriever();for (int i = 0; i < dataSource.size(); i++) {VideoInfo info = new VideoInfo();String path=dataSource.get(i);retr.setDataSource(path);String rotation = retr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);String width = retr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);String height = retr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);String duration = retr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);info.path=path;info.rotation = Integer.parseInt(rotation);info.width = Integer.parseInt(width);info.height = Integer.parseInt(height);info.duration = Integer.parseInt(duration);mInfoList.add(info);}}

这里的VideoInfo,其实就是自定义的一个视频信息的bean

       然后就是prepare方法,初始化多个播放器,并且添加到mPlayerList中
    public void prepare() throws IOException {for (int i = 0; i < mSrcList.size(); i++) {MediaPlayer player = new MediaPlayer();player.setAudioStreamType(AudioManager.STREAM_MUSIC);player.setOnCompletionListener(this);player.setOnErrorListener(this);player.setOnPreparedListener(this);player.setDataSource(mSrcList.get(i));player.prepare();mPlayerList.add(player);if (i == 0) {mCurMediaPlayer = player;if (mCallback != null) {mCallback.onVideoChanged(mInfoList.get(0));}}}}

然后是视频的start、pause和stop,我们有多个MediaPlayer当然不可能同时进行播放,所以有一个mCurmediaPlayer,用来控制当前播放的是哪个视频。

 public void start() {mCurMediaPlayer.setSurface(surface);mCurMediaPlayer.start();if (mCallback != null) {mCallback.onVideoStart();}}public void pause() {mCurMediaPlayer.pause();if (mCallback != null) {mCallback.onVideoPause();}}public void stop() {mCurMediaPlayer.stop();}

然后,就是不同播放器的切换,当播放完一个之后,通过switchPlayer切换到下一个播放器

   @Overridepublic void onCompletion(MediaPlayer mp) {curIndex++;if (curIndex >= mSrcList.size()) {curIndex = 0;if (mCallback != null) {mCallback.onCompletion(mp);}}switchPlayer(mp);}private void switchPlayer(MediaPlayer mp) {mp.setSurface(null);if (mCallback != null) {mCallback.onVideoChanged(mInfoList.get(curIndex));}mCurMediaPlayer = mPlayerList.get(curIndex);mCurMediaPlayer.setSurface(surface);mCurMediaPlayer.start();}

上面代码中,我们看到给当前的player设置了一个显示表面,surface,而这个surface就是在VideoPreviewView中设置的,将MediaPlayer和OpenGL联系起来的关键

 public void setSurface(Surface surface) {this.surface = surface;}

然后我们的MediaPlayerWrapper类,还有就是一些接口的定义,基本上就是这样了。

     MediaPlayerWrapper和VideoDrawer都解释完成了,现在我们需要来看如何在ViewPreviewView中,将它们进行联系,从而对本地视频进行解码,然后通过OpenGL进行绘制,显示在屏幕上。
     
ViewPreviewView类
在上面的ViewPreviewView的初始化函数中,我们setRendered,然后有三个我们很熟悉的回调函数
onSurfaceCreated
onSurfaceChanged
onDrawFrame
而我们的视频播放的控件的核心其实也就是在这三个方法中
首先第一个方法中,onSurfaceCreated,
    @Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {mDrawer.onSurfaceCreated(gl,config);SurfaceTexture surfaceTexture = mDrawer.getSurfaceTexture();surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {@Overridepublic void onFrameAvailable(SurfaceTexture surfaceTexture) {requestRender();}});Surface surface = new Surface(surfaceTexture);mMediaPlayer.setSurface(surface);try {mMediaPlayer.prepare();} catch (IOException e) {e.printStackTrace();}mMediaPlayer.start();}
     我们来解释一下,在上面的代码中我们主要做了些什么,
     首先调用了ViewDrawer的onSurfaceCreated方法,上面已经说了其主要进行了纹理的创建和绑定,滤镜的初始化等等,
     然后从ViewDrawer获取到当前绑定的纹理SurfaceTexture。
     然后利用这个纹理,创建一个表面对象Surface,
     然后把这个Surface对象设置给MediaPlayer,然后就开始播放视频

然后在onSurfaceChanged和onDrawFrame方法中,主要是调用了VideoDrawer的方法

    @Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {mDrawer.onSurfaceChanged(gl,width,height);}@Overridepublic void onDrawFrame(GL10 gl) {mDrawer.onDrawFrame(gl);}

如此方式,我们就很容易的实现了利用MediaPlayer解码视频,然后利用OpenGL对视频数据进行二次处理,再显示到我们的GLSurfaceView上面。
      当然我们这里是一个视频播放的控件,肯定还有一些对外提供的接口和回调函数。就不一一解释了。然后就是在预览界面的使用 ,这个也不多说了,主要将控件写在xml中,然后给该控件设置视频的播放地址,然后进行播放即可。

本地视频解码,OpenGL美颜,视频数据编码成文件

     在上一部分的内容中,我们已经实现了本地视频的播放和加水印、美颜效果的播放。那么这一部分内容中,我们就要实现视频的编解码,并且把视频数据通过OpenGL处理之后,再保存成新的视频文件。这个和上一篇文章有一点不同就是,上篇文章是通过Camera获取数据,实时进行显示,并且实时进行录制。而现在是选定好本地视频和效果之后再进行编解码。
视频的硬解码
     之前我们已经说过,并不会涉及到一些C/C++的音视频解码库,所以我们这里解码视频通过的是Android本身的相关api,当然这些api很多都是4.1之后才出现的,所以我们并不能兼容低版本,当然我也没准备兼容低版本。我们主要使用到的api包括MediaCodec,MediaMuxer,MediaFormat等等。

我们的主要思路是,通过MediaCodec的解码器,将原视频解码成帧数据,然后通过OpenGL对视频数据进行处理,再通过MediaCodec的编码器对处理后的数据进行编码,保存成一个视频文件。

    
VideoClipper类
     我们建立一个VideoClipper的类,用于处理本地视频
     首先,我们需要初始化两个解码器,两个编码器

   public VideoClipper() {try {videoDecoder = MediaCodec.createDecoderByType("video/avc");videoEncoder = MediaCodec.createEncoderByType("video/avc");audioDecoder = MediaCodec.createDecoderByType("audio/mp4a-latm");audioEncoder = MediaCodec.createEncoderByType("audio/mp4a-latm");} catch (IOException e) {e.printStackTrace();}}

通过名字就可以看出来,我们分别初始化了音频、视频的解码器和编码器,这篇我们主要讲的是视频的操作,所以暂时不管音频。

      在编解码器开始正式工作的时候,我们需要先对编解码器进行初始化:
    private void initVideoCodec() {//不对视频进行压缩int encodeW = videoWidth;int encodeH = videoHeight;//设置视频的编码参数MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", encodeW, encodeH);mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 3000000);mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);videoEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);inputSurface = new InputSurface(videoEncoder.createInputSurface());inputSurface.makeCurrent();videoEncoder.start();VideoInfo info = new VideoInfo();info.width = videoWidth;info.height = videoHeight;info.rotation = videoRotation;outputSurface = new OutputSurface(info);outputSurface.isBeauty(isOpenBeauty);videoDecoder.configure(videoFormat, outputSurface.getSurface(), null, 0);videoDecoder.start();//解码器启动}

在上面的代码中,就包括了很关键的代码,就是创建了一个InputSurface和一个OutputSurface。而这两个类原型其实来自于谷歌工程师编写的一个音视频处理的项目grafika。然后我们进行了一些改造。

       这两个类主要涉及到OpenGL和EGL相关的知识点,我们暂时只讲我们会涉及到部分。
       从上面的代码,我们看到了在OutputSurface中,我们通过isBeauty(boolean isBeauty)方法设置了是否开启美颜,而这里的isOpenBeauty,就是VideoClipper对外提供的设置接口。

   /** 开启美颜 */public void showBeauty(){isOpenBeauty = true;}

然后我们去看OutputSurface的isBeauty方法

    public void isBeauty(boolean isBeauty){mDrawer.isOpenBeauty(isBeauty);}

是不是发现了一个眼熟的东西,对就是mDrawer。该mDrawer其实就是,在OuputSurface初始化的时候创建的一个VideoDrawer

    mDrawer = new VideoDrawer(MyApplication.getContext(),MyApplication.getContext().getResources());

而这个VideoDrawer在OutputSurface中的主要用法如下,首先在setup函数中进行初始化

   private void setup(VideoInfo info) {mDrawer = new VideoDrawer(MyApplication.getContext(),MyApplication.getContext().getResources());mDrawer.onSurfaceCreated(null,null);mDrawer.onSurfaceChanged(null,info.width,info.height);//在VideoDrawer创建surfaceTexture之后,提供出来mSurfaceTexture = mDrawer.getSurfaceTexture();mSurfaceTexture.setOnFrameAvailableListener(this);mSurface = new Surface(mSurfaceTexture);}

主要就是先初始化,然后把他内部创建绑定的纹理,提供出来,并且创建一个Surface,这个Surface,通过下面的代码提供出去

    /** Returns the Surface that we draw onto.*/public Surface getSurface() {return mSurface;}

并且,最终是设置在了解码器里面。

    videoDecoder.configure(videoFormat, outputSurface.getSurface(), null, 0);

如此一来,我们通过解码器解码的数据,就会经过OutputSurface和VideoDrawer,然后不多说,和上面一样的,在VideoDrawer里面对数据进行处理。加上美白和水印等

然后在OutputSurface的drawImage方法中,调用mDrawer的onDrawFrame方法进行图像处理    
    /** Draws the data from SurfaceTexture onto the current EGL surface.*/public void drawImage() {mDrawer.onDrawFrame(null);} 

其实这里的原理和上面的预览视频是一样的

预览视频是通过MediaPlayer解码视频,然后返回到一个Surface上面,再经VideoDrawer的处理,最终呈现到界面上。
这里编解码视频,是由我们自己初始化解码器,对视频进行解码,然后通过一个Surface,把数据传递到VideoDrawer上进行处理,最后再通过InputSurface把处理后的数据给到编码器,进行二次编码,形成新的视频文件
然后,我们在视频的解码器,解出每一帧数据的时候,对数据进行OpenGL的处理,也就是调用outputSurface和inputSurface的相关方法即可    
 boolean doRender = (info.size != 0 && info.presentationTimeUs -  firstSampleTime > startPosition);decoder.releaseOutputBuffer(index, doRender);if (doRender) {// This waits for the image and renders it after it arrives.outputSurface.awaitNewImage();outputSurface.drawImage();// Send it to the encoder.inputSurface.setPresentationTime(info.presentationTimeUs * 1000);inputSurface.swapBuffers();}

相应的,音视频的编解码代码,网上有许多了,我们这里暂时不做讲解。可能会在后面的文章中,对这部分的内容进行补充。

本地视频预览效果

处理后视频播放效果

从截图可以看到,其实我们这里还存在一些小问题
第一个就是预览的时候水印图片较小,而二次编码完成的水印变大了。原因是,我们的水印是一个bitmap图片,他的宽高的像素点是固定的,预览时候界面的宽高是全屏的,也就是说是手机屏幕的宽高,而编码时候,视频的宽高是原视频的宽高,我们并没有对原视频大小进行缩放,所以会存在水印大小位置不太对的情况,可以通过对水印大小进行缩放来修改,实现所见即所得。
第二个就是,我们在VideoDrawer中对水印和美颜效果的绘制流程是先绘制水印,然后再绘制美颜效果,这样其实不太好,因为美颜是全局的,也会对水印的展示效果造成影响,所以最好是修改一下加美颜滤镜和加水印的流程,来避免这个问题。可以先绘制美颜滤镜,再加上水印。

总结

通过上面两部分的讲解,我们很轻松的就实现了本地视频预览,然后预览过程中加上美颜和水印,然后解码视频,通过OpenGL处理视频数据,再编码视频。其实很核心的一个点就是MediaPlayer和视频解码器MediaCodec都可以传入一个Surface,对解码出来的数据进行接收,就给了我们通过OpenGL处理数据的机会。非常重要的一个类就是VideoDrawer,涉及到很多OpenGL的使用。
本篇我们实现的是如何给本地视频加上水印和美颜滤镜,下一篇文章,就是在这两篇的基础上,实现给视频加上各种乱七八糟好看或者不好看的滤镜。
因为个人水平有限,难免有错误和不足之处,还望大家能包涵和提醒。谢谢啦!!!
其他
     项目的github地址
       VideoEdtior-for-Android

Android视频编辑器(三)给本地视频加水印和美颜滤镜相关推荐

  1. 一个 Android 的视频编辑器,包括了视频录制、剪切、增加 bgm、美白、加滤镜、加水印等多种功能

    VideoEditor-For-Android 项目地址:qqchenjian318/VideoEditor-For-Android  简介:一个 Android 的视频编辑器,包括了视频录制.剪切. ...

  2. Android VR Player(全景视频播放器) [6]:视频列表的实现-本地视频

    Android VR Player(全景视频播放器) [6]:视频列表的实现-本地视频 (本篇博客参考<Android第一行代码(第二版)>中关于RecyclerView的部分) 列表的实 ...

  3. python播放本地视频_python opencv 读取本地视频文件 修改ffmpeg的方法

    Python + opencv 读取视频的三种情况: 情况一:通过摄像头采集视频 情况二:通过本地视频文件获取视频 情况三:通过摄像头录制视频,再读取录制的视频 摄像头采集.本地视频文件的读取.写视频 ...

  4. 腾讯视频如何设置关闭本地视频后自动打开主界

    1.点击主页面右上角的三条横杠 腾讯视频如何设置关闭本地视频后自动打开主界面 2.点击[设置] 腾讯视频如何设置关闭本地视频后自动打开主界面 3.点击左侧的[高级设置] 腾讯视频如何设置关闭本地视频后 ...

  5. PC电脑 屏幕竖直截长屏、本地视频转码、本地视频转gif动画、gif压缩等

    PC电脑 屏幕竖直截长屏.本地视频转码.本地视频转gif动画.gif压缩等 写博客贵在坚持.为了让自己和一同进步的人,能够快速理解,并看懂自己的博客. 需要自己对博客编撰尽量完善,要配图.配代码.配动 ...

  6. C# Umeditor 编辑器上传本地视频、本地文件

    Umeditor 最近在做一个网站,涉及到网站文章的编辑.Umeditor是一个很好的选择,但是看了一下Umeditor,发现不能上传本地视频和本地文件,而项目又要得比较急,所以以我觉得最快的方式修改 ...

  7. ffmpeg分割视频,制作gif图,加水印、去水印,视频拼接

    一.分割视频 进入ffmpeg的目录: (1)执行从0分钟开始,剪切5分钟: ./ffmpeg -ss 00:00:00 -i /111/Movies/a2009.mp4 -t 00:05:00 a2 ...

  8. 史林枫:C#.NET利用ffmpeg操作视频实战(格式转换,加水印 一步到位)

    ffmpeg.exe是大名鼎鼎的视频处理软件,以命令行参数形式运行.网上也有很多关于ffmpeg的资料介绍.但是在用C#做实际开发时,却遇到了几个问题及注意事项,比如如何无损处理视频?如何在转换格式的 ...

  9. 给图片加上水印php视频,如何使用PHP给图片加水印

    为了防止辛苦做出来的图片被盗用,很多照片都会加上水印,可以直接用图片工具添加水印再上传,但PHP中就可以实现给图片加水印的功能,本文章向码农们介绍 php 给图片加水印的两种方法,感兴趣的码农可以参考 ...

最新文章

  1. 属于python文件的操作有_Python的文件操作
  2. Verizon部署美国最大小型基站系统
  3. mysql concat 变量_MySQL 字符串连接CONCAT()函数
  4. Key/Value之王Memcached初探:一、掀起Memcached的盖头来
  5. maven常见问题处理(3-3)Gradle编译时下载依赖失败解决方法
  6. wordpress api ajax,Woocommerce rest api - 在wordpress中通过ajax创建产品
  7. Golang学习:生成GIF动画
  8. 黑马程序员之Web前端全栈 · 阶段一 前端开发基础 (1)
  9. matlab imcrop 用法
  10. win10系统hp笔记本开机黑屏一段时间解决方法
  11. IP和子网掩码和网关的关系
  12. Python 3 《List》入门练习
  13. token 微信access 过期_.Net微信开发之如何解决access_token过期问题
  14. 2021-2027全球与中国可待因药品市场现状及未来发展趋势
  15. 2022年汽车配件市场分析
  16. Oracle 状告 Google 侵犯专利
  17. Group by 分组详解
  18. android获取手机型号和手机厂商
  19. 项目四 CentOS使用kubeadm部署工具部署测试环境的K8s集群---Kubectl命令使用以及安装dashboard界面
  20. NC15446 wyh的物品

热门文章

  1. ionic升华过程8-cordova插件+mui小案例
  2. 别再盲目复制Compound代码了,Defi借贷项目漏洞分析
  3. [JVM]了断局: 堆外内存无法 [ -XX:MaxDirectMemorySize ] 限制
  4. ORA-00204报错
  5. 极智Coding | C 和 C++ 读存 bin 文件方法
  6. 【油猴Tampermonkey】脚本安装教程+自用脚本推荐
  7. 儒略历--Java代码(附带发现了一些问题)
  8. 3个月前被裁员了,心情跌落谷底,直到学姐给了我这份面试文档…
  9. 谷歌地图启用全新卫星图:细节更清晰,色彩更丰富
  10. Python利用百度地图获取两地距离 最详细过程和源代码