Giter Site home page Giter Site logo

fahimfarhan / simplevideoeditor Goto Github PK

View Code? Open in Web Editor NEW
48.0 3.0 14.0 1.43 MB

A very simple free and open-source android video editor made with Mp4Composer that allows you to crop, trim and apply filters :)

License: MIT License

Java 94.83% CMake 0.22% C 0.72% Kotlin 4.23%
android video editor videoeditor android-video-editor simple-video-editor java kotlin mp4composer-android android-gpuimage freddyfang-android-video-trimmer video-crop video-filtering

simplevideoeditor's Introduction

SimpleVideoEditor

Acknowledgement

Special thanks to:

  1. MasayukiSuda/Mp4Composer-android
  2. cats-oss/android-gpuimage

๐ŸŽ‰ The library is now available via jitpack.io ๐Ÿš€

Warning Please note that I should change some file names, variables in strings.xml etc. Otherwise they may conflict with user's app resulting unprecedented problems. Sorry for inconvenience.

To import the library: Add it in your root build.gradle at the end of repositories:

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.0.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"    // <-- ADD THIS LINE
    }
}
allprojects {
		repositories {
			// ...
			maven { url 'https://www.jitpack.io' }  // <-- ADD THIS LINE
		}
	}

Next, open your app's build.gradle file, and add this at the top:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

And in the android block, add this:

android{
    // other configs
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
        // other configs
}

Finally, add the dependency

dependencies {
        implementation 'com.github.fahimfarhan:SimpleVideoEditor:v1.0'
}

That's it!

todo:

  1. study some photo editors like burhanrashid52/PhotoEditor, and implement text, stickers etc support

Crop Explanation

The crop mechanism was not well documented in the Mp4Composer-android library, so I had to go through a lot of trials and errors to understand what the library was actually doing. I am quite sure this is what the library input means. For simplicity, assume that our video frame does not change its shape, but our crop area changes shape / moves when the user drags / moves / does something.

crop math

Here as you can see, the video frame has height H pixel, width W pixel and so its center is (W/2, H/2).

So, AspectRatio, r = cropQuadWidth : cropQuadHeight = W:H

Suppose initially the crop quad has exactly the same dimensions as the video frame, that is, it has same height, width and center (W/2, H/2). Now the user uses pinch gesture to resize the crop quad by saving the aspect ratio r and keeping the center where it is. Now suppose the user uses a drag gesture to move the crop quad so that its new center is (x0,y0).

Therefore, the translation is

    dx = (x0 - W/2)  pixel,  
    dy = (y0 - H/2)  pixel

We can represent the crop quad with its 4 corner points: p0, p1, p2, p3 as shown in the picture. Since our aspect ratio is fixed, our crop quad also has aspect ratio r.

Our crop quads newWidth = (p3.x - p0.x) pixel newHeight = newWidth/r = (p3.y - p0.y) pixel Now for some reason you need to divide dx with newWidth, dy with newHeight and multiply them with 2. Don't ask me why. The library input wants this :/ So

    translationX = dx * 2 / newWidth;
    translationY = dy * 2 / newHeight;

Therefore, on pinch gesture, the crop quad has a scaling factor, scalingFactor = initialWidthOfCropQuad / finalWidthOfCropQuad => scalingFactor = W / newWidth = W / (p3.x - p0.x) = H / newHeight = H / (p3.y - p0.y) Just use a formula that you like. If you implement a rotation in your video editor, make sure to save it in a variable rotation = theta. I didnot use this feature, so I don't remember details (degree or radian, clockwise, anticlockwise etc) at the time of writing. Since I am not rotating, my rotation is zero. So rotation = 0

Now that we have all the input parameters ready, we can create a FillModeCustomItem object like this:

        FillModeCustomItem fillModeCustomItem = new FillModeCustomItem(
                scalingFactor,
                rotation,
                translateX,
                translateY,
                W ,         // the video Width = W pixel
                H           // the video Height = H pixel
        );

And finally pass this fillModeCustomItem into mp4composer to get the desired crop area. You need to specify the output videos height and width. Let outputVideoWidthPx = 720 pixel and outputVideoHeightPx = outputWidth/r which is the crop quad's width and height. It's not entirely clear to my how / why this works. The original developers gave a sample where the crop area was fixed and the video frame resized itself. So perhaps internally, the library takes a quad of outputVideoWidthPx, outputVideoHeightPx centered at (x0, y0), then zooms the video frame (using that scalingFactor) and translates translationX, translationY amount to track the actual crop area.

Finally, you do this in java:

        mp4Composer = new Mp4Composer(selectedVideoUri, destPath, activity, mylogger);
        mp4Composer
                .size(outputVideoWidthPx, outputVideoHeightPx) // fake it till you make it
                .fillMode(FillMode.CUSTOM)
                .customFillMode(fillModeCustomItem);
        // todo: other inputs according to your requirements
        mp4Composer.start();                

Example:

Please check out VideoEditorPresenter.java#L83, it shows my implementation.

You can also download my project, run it and try to have a better understanding. If you have any questions, feel free to ask me and I'll try my utmost to answer them. Thank you.

simplevideoeditor's People

Contributors

fahimfarhan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

simplevideoeditor's Issues

Crop Video with a special view

Hi first of all , thank you for your library . Im using your library to let my users crop their video in variables aspect ratio , im using this This CropVideoView as a replacement for your Simplecropview , the crop is not working corretly as it should , what im doing wrong ? Here s my code ` Uri fileUri = Uri.fromFile(new File(FinalSingleNewPath));
String destPath = getVideoFilePath();

                                        Log.e(TAG, "translateX = "+  mVideoCropView.getRealPositionX() + " translateY = "+  mVideoCropView.getRealPositionY() );
                                        Log.e(TAG, "resolution.width = "+mVideoCropView.getWidth()+" resolution.getHeight = "+mVideoCropView.getHeight());

                                        FillModeCustomItem fillModeCustomItem = new FillModeCustomItem(
                                                mVideoCropView.getScaleX(),
                                                mVideoCropView.getRotate(),
                                                (int) mVideoCropView.getRealPositionX(),
                                                (int) mVideoCropView.getRealPositionY(),
                                                mVideoCropView.getVideoWidth() ,
                                                mVideoCropView.getVideoHeight()
                                        );


                                        aftercorp.final_flash.EditPostFile.GlFilterGroup glFilterGroup;
                                        if(composerGlFilter == null) {
                                         composerGlFilter = new GlFilter();
                                        }

                                        glFilterGroup  = new GlFilterGroup(new GlFilter()); // the default filter
                                        Log.e(TAG, "currentGlFilter is null");


                           

                                        final Logger mylogger = new Logger() {
                                            @Override
                                            public void debug(String tag, String message) {
                                                Log.d(tag, message);
                                            }

                                            @Override
                                            public void error(String tag, String message, Throwable error) {
                                                Log.e(tag, "Message: "+message + ". Error: "+error.getLocalizedMessage());
                                            }

                                            @Override
                                            public void warning(String tag, String message) {
                                                Log.w(tag, message);
                                            }
                                        };


                                        mp4Composer = null;
                                        mp4Composer = new Mp4Composer(fileUri, destPath, SinglePicture.this, mylogger);
                                        mp4Composer
                                             .size(mVideoCropView.getWidth(), mVideoCropView.getHeight()) // Dont fake it anymore
                                                .fillMode(FillMode.CUSTOM)
                                                .customFillMode(fillModeCustomItem);



                                        if(composerGlFilter != null){
                                            mp4Composer.filter(composerGlFilter);
                                        }else{
                                            mp4Composer.filter(glFilterGroup);
                                        }

                                        mp4Composer.trim(0,  mVideoCropView.getDuration())
                                                .listener(new Mp4Composer.Listener() {
                                                    @Override
                                                    public void onProgress(double progress) {
                                                        Log.d(TAG, "onProgress = " + progress);
                                                        new Thread()
                                                        {
                                                            public void run()
                                                            {
                                                                CropVIdeo.this.runOnUiThread(new Runnable()
                                                                {
                                                                    public void run()
                                                                    {
                                                                        //Do your UI operations like dialog opening or Toast here
                                                                        Toast.makeText( CropVIdeo.this, "onProgress = " + progress, Toast.LENGTH_SHORT).show();
                                                                    }
                                                                });
                                                            }
                                                        }.start();
                                                    }

                                                    @Override
                                                    public void onCompleted() {
                                                        Log.d(TAG, "onCompleted()");

                                                        new Thread()
                                                        {
                                                            public void run()
                                                            {
                                                                CropVIdeo.this.runOnUiThread(new Runnable()
                                                                {
                                                                    public void run()
                                                                    {
                                                                        //Do your UI operations like dialog opening or Toast here
                                                                        Toast.makeText( CropVIdeo.this, "codec complete path = " + destPath, Toast.LENGTH_SHORT).show();
                                                                    }
                                                                });
                                                            }
                                                        }.start();
                                                    }

                                                    @Override
                                                    public void onCanceled() {
                                                        Log.d(TAG, "onCanceled");

                                                        new Thread()
                                                        {
                                                            public void run()
                                                            {
                                                                CropVIdeo.this.runOnUiThread(new Runnable()
                                                                {
                                                                    public void run()
                                                                    {
                                                                        //Do your UI operations like dialog opening or Toast here
                                                                        Toast.makeText( CropVIdeo.this, "videoProcessing onCanceled", Toast.LENGTH_SHORT).show();
                                                                    }
                                                                });
                                                            }
                                                        }.start();
                                                    }

                                                    @Override
                                                    public void onFailed(Exception exception) {
                                                        Log.e(TAG, "onFailed()", exception);

                                                        new Thread()
                                                        {
                                                            public void run()
                                                            {
                                                                CropVIdeo.this.runOnUiThread(new Runnable()
                                                                {
                                                                    public void run()
                                                                    {
                                                                        //Do your UI operations like dialog opening or Toast here
                                                                        Toast.makeText( CropVIdeo.this, exception.getMessage(), Toast.LENGTH_SHORT).show();

                                                                    }
                                                                });
                                                            }
                                                        }.start();

                                                    }
                                                });

                                        mp4Composer.start();

`

Crop fail with aspect ratio 16 : 9

The crop method make crash your demo if you use 16 : 9 as aspect ratio with the Simplecropview
in the setAspectRatio method

Here the Logcat

2021-01-06 05:43:59.168 18227-18227/app.fahimfarhan.start E/MainActivity: path = /storage/emulated/0/DCIM/Camera/8409335596234021ecba141032ee78ec.mp4 2021-01-06 05:43:59.810 18227-18227/app.fahimfarhan.start E/VideoEditorFragment2: onViewCreated 2021-01-06 05:43:59.810 18227-18227/app.fahimfarhan.start E/VideoEditorPresenter: VideoEditorPresenter#displayTrimmerView() 2021-01-06 05:43:59.820 18227-18227/app.fahimfarhan.start E/VideoEditorPresenter: getRealPathFromMediaData() path = /storage/emulated/0/DCIM/Camera/8409335596234021ecba141032ee78ec.mp4 2021-01-06 05:43:59.820 18227-18227/app.fahimfarhan.start E/VideoEditorPresenter: filePath = /storage/emulated/0/DCIM/Camera/8409335596234021ecba141032ee78ec.mp4 2021-01-06 05:43:59.847 18227-18227/app.fahimfarhan.start E/VideoEditorFragment2: start = 0 end = 8500 2021-01-06 05:44:00.148 18227-18458/app.fahimfarhan.start E/ACodec: [OMX.hisi.video.decoder.avc] setPortMode on output to DynamicANWBuffer failed w/ err -2147483648 2021-01-06 05:44:00.149 18227-18458/app.fahimfarhan.start E/libc: Access denied finding property "ro.kirin.product.platform" 2021-01-06 05:44:00.149 18227-18458/app.fahimfarhan.start E/HwExtendedCodec: mime: video/avc matching compontent failed! 2021-01-06 05:44:00.288 18227-18439/app.fahimfarhan.start E/AudioTrack: StreamType not music do not upload bigdata 2021-01-06 05:44:00.610 18227-18227/app.fahimfarhan.start E/VideoEditorPresenter: path = /storage/emulated/0/DCIM/Camera/8409335596234021ecba141032ee78ec.mp4 2021-01-06 05:44:02.245 18227-18227/app.fahimfarhan.start E/EditOptionsAdapter: click detected! 1 2021-01-06 05:44:03.672 18227-18227/app.fahimfarhan.start E/VideoEditorFragment2: ivPortrait clicked! 2021-01-06 05:44:09.007 18227-18227/app.fahimfarhan.start E/VideoEditorFragment2: ivLandScape clicked! 2021-01-06 05:44:11.354 18227-18227/app.fahimfarhan.start E/VideoEditorFragment2: ivPortrait clicked! 2021-01-06 05:44:14.373 18227-18227/app.fahimfarhan.start E/VideoEditorPresenter: translateX = -0.0014388489 translateY = -0.0014388489 2021-01-06 05:44:14.374 18227-18227/app.fahimfarhan.start E/VideoEditorPresenter: resolution.width = 576 resolution.getHeight = 1024 2021-01-06 05:44:14.397 18227-18227/app.fahimfarhan.start E/VideoEditorPresenter: currentGlFilter is null 2021-01-06 05:44:14.526 18227-18595/app.fahimfarhan.start E/libc: Access denied finding property "ro.kirin.product.platform" 2021-01-06 05:44:14.526 18227-18595/app.fahimfarhan.start E/HwExtendedCodec: mime: video/3gpp matching compontent failed! 2021-01-06 05:44:14.564 18227-18597/app.fahimfarhan.start E/ACodec: [OMX.google.h263.encoder] ERROR(0x80001001) 2021-01-06 05:44:14.564 18227-18597/app.fahimfarhan.start E/ACodec: signalError(omxError 0x80001001, internalError -2147483648) 2021-01-06 05:44:14.564 18227-18596/app.fahimfarhan.start E/MediaCodec: Codec reported err 0x80001001, actionCode 0, while in state 6 2021-01-06 05:44:14.577 18227-18595/app.fahimfarhan.start E/libc: Access denied finding property "ro.kirin.product.platform" 2021-01-06 05:44:14.577 18227-18595/app.fahimfarhan.start E/HwExtendedCodec: mime: video/avc matching compontent failed! 2021-01-06 05:44:14.585 18227-18605/app.fahimfarhan.start E/ACodec: [OMX.hisi.video.decoder.avc] setPortMode on output to DynamicANWBuffer failed w/ err -2147483648 2021-01-06 05:44:14.631 18227-18605/app.fahimfarhan.start E/BufferQueueProducer: [SurfaceTexture-1-18227-2] cancelBuffer: BufferQueue has been abandoned 2021-01-06 05:44:14.663 18227-18595/app.fahimfarhan.start E/Mp4ComposerEngine: Message: Could not shutdown mediaExtractor, codecs and mediaMuxer pipeline.. Error: null 2021-01-06 05:44:14.663 18227-18595/app.fahimfarhan.start E/Mp4Composer: Message: Unable to compose the engine. Error: null 2021-01-06 05:44:14.664 18227-18595/app.fahimfarhan.start E/VideoEditorPresenter: onFailed() java.lang.IllegalStateException at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method) at android.media.MediaCodec.dequeueOutputBuffer(MediaCodec.java:2705) at com.fahimfarhan.simplevideoeditor.mp4composer.mp4compose.composer.VideoComposer.drainEncoder(VideoComposer.java:233) at com.fahimfarhan.simplevideoeditor.mp4composer.mp4compose.composer.VideoComposer.stepPipeline(VideoComposer.java:123) at com.fahimfarhan.simplevideoeditor.mp4composer.mp4compose.composer.Mp4ComposerEngine.runPipelines(Mp4ComposerEngine.java:279) at com.fahimfarhan.simplevideoeditor.mp4composer.mp4compose.composer.Mp4ComposerEngine.compose(Mp4ComposerEngine.java:142) at com.fahimfarhan.simplevideoeditor.mp4composer.mp4compose.composer.Mp4Composer$2.run(Mp4Composer.java:299) at java.util.concurrent.ThreadPoolExecutor.processTask(ThreadPoolExecutor.java:1187) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1152) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:784) 2021-01-06 05:44:14.666 18227-18595/app.fahimfarhan.start E/AndroidRuntime: FATAL EXCEPTION: pool-1-thread-1 Process: app.fahimfarhan.start, PID: 18227 java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare() at android.widget.Toast$TN.<init>(Toast.java:393) at android.widget.Toast.<init>(Toast.java:117) at android.widget.Toast.makeText(Toast.java:280) at android.widget.Toast.makeText(Toast.java:270) at com.fahimfarhan.simplevideoeditor.videoeditor.VideoEditorPresenter$2.onFailed(VideoEditorPresenter.java:217) at com.fahimfarhan.simplevideoeditor.mp4composer.mp4compose.composer.Mp4Composer.notifyListenerOfFailureAndShutdown(Mp4Composer.java:351) at com.fahimfarhan.simplevideoeditor.mp4composer.mp4compose.composer.Mp4Composer.access$000(Mp4Composer.java:36) at com.fahimfarhan.simplevideoeditor.mp4composer.mp4compose.composer.Mp4Composer$2.run(Mp4Composer.java:330) at java.util.concurrent.ThreadPoolExecutor.processTask(ThreadPoolExecutor.java:1187) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1152) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:784)

Nice project, are you able to add a Sketch filter?

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Describe the solution you'd like
A clear and concise description of what you want to happen.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

How to make the crop faster ?

How to make the croping faster it take beetween 10 and 30 seconds to crop a video , My users can crop beetween one and 10 video so I need to crop it faster

Changing the aspect ratio

// todo: idk, change it later to 16:9, 3:4, 1:1 ... ... via some method I guess

I saw your todo so are you coming with this or not?
I really need that 16:9 ratio

App Crash when try to decode Cropped Video

Describe the bug
A clear and concise description of what the bug is.

To Reproduce
Steps to reproduce the behavior:

  1. Go to Gallery and Select Video
  2. Change Pre added shape
  3. And then Click on Continue Button
  4. And the in log cat Error showing

Message: This devicel cannot codec with that setting. Check width, height, bitrate and video format.. Error: Error 0xffffec77

Smartphone (please complete the following information):

  • Device: Samsung
  • OS: Android 10

Filter feature is not working.

When i change filter and press continue to save. it saves the original video not the filtered one. You're going to fix this or not?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.