├── .github └── workflows │ ├── deploy-snapshot.yml │ ├── gradle-wrapper-validation.yml │ ├── merge-check.yml │ └── publish-maven-central.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── RELEASING.md ├── build.gradle ├── gradle.properties ├── gradle ├── publishing.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── litr-demo ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── linkedin │ │ └── android │ │ └── litr │ │ ├── demo │ │ ├── BaseTransformationFragment.java │ │ ├── DemoCase.java │ │ ├── DemoCasesAdapter.java │ │ ├── InfoActivity.java │ │ ├── MainActivity.java │ │ ├── MainFragment.java │ │ ├── MediaPickerListener.java │ │ ├── data │ │ │ ├── AudioTrackFormat.java │ │ │ ├── AudioVolumeConfig.java │ │ │ ├── Converter.java │ │ │ ├── EmptyVideoPresenter.kt │ │ │ ├── FreeTransformVideoGlPresenter.kt │ │ │ ├── GenericTrackFormat.java │ │ │ ├── MediaTrackFormat.java │ │ │ ├── MediaTransformationListener.java │ │ │ ├── MuxVideoAndAudioPresenter.kt │ │ │ ├── RecordAudioPresenter.kt │ │ │ ├── RecordCameraPresenter.kt │ │ │ ├── SharedMediaStoragePublisher.kt │ │ │ ├── SourceMedia.java │ │ │ ├── SquareCenterCropPresenter.kt │ │ │ ├── TargetAudioTrack.java │ │ │ ├── TargetMedia.java │ │ │ ├── TargetTrack.java │ │ │ ├── TargetVideoConfiguration.java │ │ │ ├── TargetVideoTrack.java │ │ │ ├── TranscodeAudioPresenter.kt │ │ │ ├── TranscodeToVp9Presenter.kt │ │ │ ├── TranscodeVideoGlPresenter.kt │ │ │ ├── TranscodingConfigPresenter.java │ │ │ ├── TransformationPresenter.kt │ │ │ ├── TransformationState.java │ │ │ ├── TrimConfig.java │ │ │ ├── VideoFiltersPresenter.kt │ │ │ ├── VideoTrackFormat.java │ │ │ └── VideoWatermarkPresenter.kt │ │ ├── fragment │ │ │ ├── CameraSizes.kt │ │ │ ├── DemoFilter.java │ │ │ ├── EmptyVideoFragment.kt │ │ │ ├── ExtractFramesFragment.kt │ │ │ ├── ExtractedFramesAdapter.kt │ │ │ ├── FreeTransformVideoGlFragment.java │ │ │ ├── MediaTrackAdapter.java │ │ │ ├── MockTranscodeFragment.java │ │ │ ├── MuxVideoAndAudioFragment.kt │ │ │ ├── NativeMuxerCameraFragment.kt │ │ │ ├── NativeMuxerTranscodeFragment.kt │ │ │ ├── RecordAudioFragment.kt │ │ │ ├── RecordCamera2Fragment.kt │ │ │ ├── SquareCenterCropFragment.java │ │ │ ├── TranscodeAudioFragment.kt │ │ │ ├── TranscodeToVp9Fragment.kt │ │ │ ├── TranscodeVideoGlFragment.java │ │ │ ├── VideoFilmStripView.kt │ │ │ ├── VideoFilterPreviewFragment.java │ │ │ ├── VideoFiltersFragment.java │ │ │ └── VideoWatermarkFragment.java │ │ └── view │ │ │ ├── AudioTrackViewHolder.java │ │ │ ├── AutoFitSurfaceView.kt │ │ │ ├── GenericTrackViewHolder.java │ │ │ └── VideoTrackViewHolder.java │ │ └── utils │ │ ├── DeviceUtil.java │ │ ├── TrackMetadataUtil.java │ │ └── TransformationUtil.java │ └── res │ ├── layout │ ├── activity_info.xml │ ├── activity_main.xml │ ├── fragment_audio_record.xml │ ├── fragment_camera2_record.xml │ ├── fragment_empty_video.xml │ ├── fragment_extract_frames.xml │ ├── fragment_main.xml │ ├── fragment_mock_transcode.xml │ ├── fragment_mux_video_audio.xml │ ├── fragment_square_center_crop.xml │ ├── fragment_transcode_audio.xml │ ├── fragment_transcode_video_gl.xml │ ├── fragment_video_filter_preview.xml │ ├── fragment_video_filters.xml │ ├── fragment_video_overlay_gl.xml │ ├── fragment_video_vp9.xml │ ├── fragment_video_watermark.xml │ ├── item_audio_track.xml │ ├── item_frame.xml │ ├── item_generic_track.xml │ ├── item_video_track.xml │ ├── section_audio_volume.xml │ ├── section_pick_audio.xml │ ├── section_pick_video.xml │ ├── section_target_video_configuration.xml │ ├── section_transformation_progress.xml │ └── section_trim.xml │ ├── menu │ └── main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── file_provider_paths.xml ├── litr-ffmpeg ├── .gitignore ├── README.md ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── cpp │ ├── CMakeLists.txt │ ├── FFmpeg.h │ ├── Logging.h │ ├── MediaMuxer.cpp │ ├── MediaMuxer.h │ ├── NativeLogger.cpp │ ├── NativeMediaMuxer.cpp │ ├── build_ffmpeg.sh │ └── ffmpeg_bundled │ │ ├── README.md │ │ ├── android-libs │ │ ├── arm64-v8a │ │ │ ├── libavcodec.so │ │ │ ├── libavformat.so │ │ │ └── libavutil.so │ │ ├── armeabi-v7a │ │ │ ├── libavcodec.so │ │ │ ├── libavformat.so │ │ │ └── libavutil.so │ │ ├── x86 │ │ │ ├── libavcodec.so │ │ │ ├── libavformat.so │ │ │ └── libavutil.so │ │ └── x86_64 │ │ │ ├── libavcodec.so │ │ │ ├── libavformat.so │ │ │ └── libavutil.so │ │ └── extra │ │ └── libavutil │ │ └── avconfig.h │ └── java │ └── com │ └── linkedin │ └── android │ └── litr │ └── muxers │ ├── MediaFormatEx.kt │ ├── NativeLogger.kt │ ├── NativeMediaMuxer.kt │ ├── NativeMediaMuxerMediaTarget.kt │ ├── NativeMuxersLib.kt │ └── NativeOutputFormats.kt ├── litr-filters ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── linkedin │ └── android │ └── litr │ └── filter │ ├── audio │ ├── AudioOverlayFilter.kt │ └── VolumeFilter.kt │ └── video │ └── gl │ ├── AnimationFrameProvider.java │ ├── BaseOverlayGlFilter.java │ ├── BilateralFilter.java │ ├── BitmapOverlayFilter.java │ ├── BrightnessFilter.java │ ├── BulgeDistortionFilter.java │ ├── CgaColorspaceFilter.java │ ├── ColorBalanceFilter.java │ ├── ColorMatrixFilter.java │ ├── ColorMonochromeFilter.java │ ├── ContrastFilter.java │ ├── CrossHatchFilter.java │ ├── ExposureFilter.java │ ├── FalseColorFilter.java │ ├── FrameSequenceAnimationOverlayFilter.java │ ├── GammaFilter.java │ ├── GaussianBlurFilter.java │ ├── GlassSphereFilter.java │ ├── GrayscaleFilter.java │ ├── HalftoneFilter.java │ ├── HazeFilter.java │ ├── HueFilter.java │ ├── InversionFilter.java │ ├── KuwaharaFilter.java │ ├── LaplacianFilter.java │ ├── LevelsFilter.java │ ├── LocalBinaryPatternFilter.java │ ├── OpacityFilter.java │ ├── PixelationFilter.java │ ├── PosterizationFilter.java │ ├── RgbFilter.java │ ├── SaturationFilter.java │ ├── SepiaFilter.java │ ├── ShadowsHighlightsFilter.java │ ├── SharpenFilter.java │ ├── SolarizeFilter.java │ ├── SolidBackgroundColorFilter.java │ ├── SphereRefractionFilter.java │ ├── SwirlFilter.java │ ├── ToonFilter.java │ ├── VibranceFilter.java │ ├── VignetteFilter.java │ ├── WeakPixelInclusionFilter.java │ ├── WhiteBalanceFilter.java │ └── ZoomBlurFilter.java ├── litr ├── build.gradle └── src │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── CMakeLists.txt │ │ ├── audio-processor.cpp │ │ └── oboe_resampler │ │ │ ├── CMakeLists.txt │ │ │ ├── HyperbolicCosineWindow.h │ │ │ ├── IntegerRatio.cpp │ │ │ ├── IntegerRatio.h │ │ │ ├── KaiserWindow.h │ │ │ ├── LinearResampler.cpp │ │ │ ├── LinearResampler.h │ │ │ ├── MultiChannelResampler.cpp │ │ │ ├── MultiChannelResampler.h │ │ │ ├── PolyphaseResampler.cpp │ │ │ ├── PolyphaseResampler.h │ │ │ ├── PolyphaseResamplerMono.cpp │ │ │ ├── PolyphaseResamplerMono.h │ │ │ ├── PolyphaseResamplerStereo.cpp │ │ │ ├── PolyphaseResamplerStereo.h │ │ │ ├── README.md │ │ │ ├── SincResampler.cpp │ │ │ ├── SincResampler.h │ │ │ ├── SincResamplerStereo.cpp │ │ │ └── SincResamplerStereo.h │ └── java │ │ └── com │ │ └── linkedin │ │ └── android │ │ └── litr │ │ ├── Annotations.kt │ │ ├── MarshallingTransformationListener.java │ │ ├── MediaTransformer.java │ │ ├── MimeType.java │ │ ├── TrackTransform.java │ │ ├── TransformationJob.java │ │ ├── TransformationListener.java │ │ ├── TransformationOptions.java │ │ ├── analytics │ │ ├── TrackTransformationInfo.java │ │ └── TransformationStatsCollector.java │ │ ├── codec │ │ ├── Decoder.java │ │ ├── Encoder.java │ │ ├── Frame.java │ │ ├── MediaCodecDecoder.java │ │ ├── MediaCodecEncoder.java │ │ ├── PassthroughBufferEncoder.kt │ │ └── PassthroughDecoder.kt │ │ ├── exception │ │ ├── InsufficientDiskSpaceException.java │ │ ├── MediaSourceException.java │ │ ├── MediaTargetException.java │ │ ├── MediaTransformationException.java │ │ └── TrackTranscoderException.java │ │ ├── filter │ │ ├── BufferFilter.kt │ │ ├── GlFilter.java │ │ ├── GlFrameRenderFilter.java │ │ ├── Transform.java │ │ ├── util │ │ │ └── GlFilterUtil.java │ │ └── video │ │ │ └── gl │ │ │ ├── DefaultVideoFrameRenderFilter.java │ │ │ ├── VideoFrameRenderFilter.java │ │ │ ├── parameter │ │ │ ├── ShaderParameter.java │ │ │ ├── Uniform1f.java │ │ │ ├── Uniform1fv.java │ │ │ ├── Uniform1i.java │ │ │ ├── Uniform1iv.java │ │ │ ├── Uniform2f.java │ │ │ ├── Uniform2fv.java │ │ │ ├── Uniform2i.java │ │ │ ├── Uniform2iv.java │ │ │ ├── Uniform3f.java │ │ │ ├── Uniform3fv.java │ │ │ ├── Uniform3i.java │ │ │ ├── Uniform3iv.java │ │ │ ├── Uniform4f.java │ │ │ ├── Uniform4fv.java │ │ │ ├── Uniform4i.java │ │ │ ├── Uniform4iv.java │ │ │ ├── UniformMatrix2fv.java │ │ │ ├── UniformMatrix3fv.java │ │ │ └── UniformMatrix4fv.java │ │ │ └── shader │ │ │ └── VertexShader.java │ │ ├── frameextract │ │ ├── FrameExtractJob.kt │ │ ├── FrameExtractListener.kt │ │ ├── FrameExtractMode.kt │ │ ├── FrameExtractParameters.kt │ │ ├── VideoFrameExtractor.kt │ │ ├── behaviors │ │ │ ├── FrameExtractBehavior.kt │ │ │ └── MediaMetadataExtractBehavior.kt │ │ └── queue │ │ │ ├── ComparableFutureTask.kt │ │ │ └── PriorityExecutorUtil.kt │ │ ├── io │ │ ├── AudioRecordMediaSource.kt │ │ ├── Camera2MediaSource.kt │ │ ├── CaptureMediaSource.kt │ │ ├── MediaExtractorMediaSource.java │ │ ├── MediaMuxerMediaTarget.java │ │ ├── MediaRange.java │ │ ├── MediaSource.java │ │ ├── MediaTarget.java │ │ ├── MediaTargetSample.kt │ │ ├── MockVideoMediaSource.kt │ │ └── WavMediaTarget.kt │ │ ├── preview │ │ ├── PreviewEglConfigChooser.java │ │ ├── PreviewEglContextFactory.java │ │ ├── VideoFilterPreviewView.java │ │ └── VideoPreviewRenderer.java │ │ ├── render │ │ ├── AudioProcessor.kt │ │ ├── AudioProcessorFactory.kt │ │ ├── AudioRenderer.kt │ │ ├── FrameDropper.kt │ │ ├── GlFramebuffer.kt │ │ ├── GlRenderUtils.java │ │ ├── GlSingleFrameRenderer.kt │ │ ├── GlTexture.kt │ │ ├── GlVideoRenderer.java │ │ ├── OboeAudioProcessor.kt │ │ ├── PassthroughAudioProcessor.kt │ │ ├── Renderer.java │ │ ├── SingleFrameRenderer.kt │ │ ├── VideoRenderInputSurface.java │ │ └── VideoRenderOutputSurface.java │ │ ├── test │ │ ├── MockMediaTransformer.java │ │ └── TransformationEvent.java │ │ ├── transcoder │ │ ├── AudioTrackTranscoder.java │ │ ├── PassthroughTranscoder.java │ │ ├── TrackTranscoder.java │ │ ├── TrackTranscoderFactory.java │ │ └── VideoTrackTranscoder.java │ │ └── utils │ │ ├── ByteBufferPool.kt │ │ ├── CodecUtils.java │ │ ├── DiskUtil.java │ │ ├── MediaFormatUtils.kt │ │ ├── TimeUtils.java │ │ └── TranscoderUtils.java │ └── test │ └── java │ └── com │ └── linkedin │ └── android │ └── litr │ ├── TransformationJobShould.java │ ├── codec │ ├── PassthroughBufferEncoderShould.kt │ └── PassthroughDecoderShould.kt │ ├── io │ └── MediaMuxerMediaTargetShould.java │ ├── render │ └── AudioRendererShould.kt │ ├── transcoder │ ├── AudioTrackTranscoderShould.java │ ├── FrameDropperShould.kt │ ├── PassthroughTranscoderShould.java │ └── VideoTrackTranscoderShould.java │ └── utils │ └── TranscoderUtilsShould.java └── settings.gradle /.github/workflows/deploy-snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Deploy snapshot 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | if: ${{ !contains(github.event.head_commit.message, 'Prepare for release') }} 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Cache Gradle Files 14 | uses: actions/cache@v2 15 | with: 16 | path: | 17 | ~/.gradle/caches/ 18 | ~/.gradle/wrapper/ 19 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 20 | restore-keys: | 21 | ${{ runner.os }}-gradle- 22 | 23 | - name: Set up Java 24 | uses: actions/setup-java@v1 25 | with: 26 | java-version: 11 27 | 28 | - name: Build 29 | run: ./gradlew build 30 | 31 | - name: Publish package 32 | run: ./gradlew publishAllPublicationsToSonatypeSnapshotRepository 33 | env: 34 | SONATYPE_USER: ${{ secrets.SONATYPE_USER }} 35 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 36 | -------------------------------------------------------------------------------- /.github/workflows/gradle-wrapper-validation.yml: -------------------------------------------------------------------------------- 1 | name: "Validate Gradle Wrapper" 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - '*' 9 | 10 | jobs: 11 | validation: 12 | name: "Validation" 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: gradle/wrapper-validation-action@v1 17 | 18 | -------------------------------------------------------------------------------- /.github/workflows/merge-check.yml: -------------------------------------------------------------------------------- 1 | name: Merge checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | build: 13 | name: Build project 14 | runs-on: ubuntu-latest 15 | if: ${{ !contains(github.event.head_commit.message, 'ci skip') }} 16 | steps: 17 | - name: Checkout Repo 18 | uses: actions/checkout@v2 19 | 20 | - name: Cache Gradle Files 21 | uses: actions/cache@v2 22 | with: 23 | path: | 24 | ~/.gradle/caches/ 25 | ~/.gradle/wrapper/ 26 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 27 | restore-keys: | 28 | ${{ runner.os }}-gradle- 29 | 30 | - name: Run Gradle tasks 31 | run: ./gradlew build 32 | -------------------------------------------------------------------------------- /.github/workflows/publish-maven-central.yml: -------------------------------------------------------------------------------- 1 | name: Publish package to the Maven Central Repository 2 | on: 3 | release: 4 | types: [published] 5 | branches: 6 | - main 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Set up Java 14 | uses: actions/setup-java@v1 15 | with: 16 | java-version: 11 17 | 18 | - name: Build 19 | run: ./gradlew build 20 | 21 | - name: Publish package 22 | run: ./gradlew publishAllPublicationsToMavenCentralRepository 23 | env: 24 | SONATYPE_USER: ${{ secrets.SONATYPE_USER }} 25 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 26 | LITR_GPG_PRIVATE_KEY: ${{ secrets.LITR_GPG_PRIVATE_KEY }} 27 | LITR_GPG_PRIVATE_KEY_PASSWORD: ${{ secrets.LITR_GPG_PRIVATE_KEY_PASSWORD }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | local.properties 4 | .idea 5 | .idea/caches 6 | .idea/libraries 7 | .idea/modules.xml 8 | .idea/workspace.xml 9 | .idea/navEditor.xml 10 | .idea/assetWizardSettings.xml 11 | .DS_Store 12 | build 13 | captures 14 | .externalNativeBuild 15 | .cxx 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to LiTr 2 | 3 | LiTr is designed to encourage contribution. Transformation process is divided into five distinct steps, and components for each step are pluggable: 4 | - Overriding `MediaSource` allows reading data from sources Android's MediaExtractor can't, such as stream from camera or unsupported container formats 5 | - Implementing custom `Decoder` and/or `Encoder` allows experimentation with not yet supported codecs (for example, AV1) 6 | - Overriding `MediaTarget` allows writing data using custom muxer (MKV, for instance) 7 | - Custom `Renderer` can do things beyond simple resizing - ML based frame modification, audio mixing, etc. 8 | 9 | In addition, it should be quite easy to develop and contribute new filters by implementing `GlFilter` interface. Please contribute filters into `litr-filters` library. 10 | 11 | ## Contribution Agreement 12 | 13 | As a contributor, you represent that the code you submit is your original work or that of your employer 14 | (in which case you represent you have the right to bind your employer). By submitting code, you 15 | (and, if applicable, your employer) are licensing the submitted code to LinkedIn and the open source 16 | community subject to the BSD 2-Clause license. 17 | 18 | ## Responsible Disclosure of Security Vulnerabilities 19 | 20 | Please do not file reports on Github for security issues. Please send vulnerability reports to 21 | security@linkedin.com preferably with the title "Github linkedin/ - ". 22 | 23 | ## Tips for Getting Your Pull Request Accepted 24 | 25 | - Make sure all new features are tested and the tests pass. 26 | - Bug fixes must include a test case demonstrating the error that it fixes. 27 | - Open an issue first and seek advice for your change before submitting a pull request. Large features which have never been discussed are unlikely to be accepted. You have been warned. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-CLAUSE LICENSE 2 | Copyright 2019 LinkedIn Corporation 3 | All Rights Reserved. 4 | Redistribution and use in source and binary forms, with or 5 | without modification, are permitted provided that the following 6 | conditions are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 15 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 16 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 17 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 18 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | 1. Change the version in `gradle.properties` to a non-SNAPSHOT version. 4 | 2. Update the `CHANGELOG.md` for the impending release. 5 | 3. Update the `README.md` with the new version. 6 | 4. `git commit -am "Prepare for release X.Y.Z."` (where X.Y.Z is the new version) 7 | 5. `git tag -a X.Y.Z -m "Version X.Y.Z"` (where X.Y.Z is the new version) 8 | 6. Update the `gradle.properties` to the next SNAPSHOT version. 9 | 7. `git commit -am "Prepare next development version."` 10 | 8. `git push && git push --tags` 11 | 9. Create a new release in the releases tab on GitHub 12 | 10. Wait for the [publish-maven-central.yml](.github/workflows/publish-maven-central.yml) action to complete. 13 | 11. Visit [Sonatype Nexus](https://oss.sonatype.org/) and promote the artifact. 14 | 15 | ## How it works 16 | 17 | The [deploy-snapshot.yml](.github/workflows/deploy-snapshot.yml) workflow runs on every 18 | push to the main branch as long as the commit message does not contain `Prepare for release`. This 19 | workflow calls Gradle to publish to the Sonatype snapshot repository. 20 | 21 | For actual releases, there is a separate [publish-maven-central.yml](.github/workflows/publish-maven-central.yml) 22 | workflow which runs after a new release is created in the GitHub UI. This will call Gradle on the 23 | tagged release commit and upload to the staging repository. After that completes, you will need to 24 | go and promote the artifacts to production. 25 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.20' 3 | 4 | repositories { 5 | google() 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.3.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | 20 | group = GROUP_ID 21 | version = VERSION_NAME 22 | } 23 | 24 | ext { 25 | minSdkVersion = 18 26 | targetSdkVersion = 30 27 | compileSdkVersion = 33 28 | buildToolsVersion = "33.0.1" 29 | } 30 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | # AndroidX package structure to make it clearer which packages are bundled with the 20 | # Android operating system, and which are packaged with your app's APK 21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 22 | android.useAndroidX=true 23 | 24 | GROUP_ID=com.linkedin.android.litr 25 | VERSION_NAME=1.5.8-SNAPSHOT 26 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkedin/LiTr/070479ada51176a4f2014bf799bb569448ed0027/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Feb 19 05:51:03 UZT 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /litr-demo/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'kotlin-kapt' 5 | } 6 | 7 | android { 8 | compileSdkVersion rootProject.ext.compileSdkVersion 9 | buildToolsVersion rootProject.ext.buildToolsVersion 10 | 11 | namespace 'com.linkedin.android.litr.demo' 12 | 13 | defaultConfig { 14 | applicationId "com.linkedin.android.litr.demo" 15 | minSdkVersion rootProject.ext.minSdkVersion 16 | targetSdkVersion rootProject.ext.targetSdkVersion 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | multiDexEnabled true 19 | } 20 | 21 | compileOptions { 22 | sourceCompatibility JavaVersion.VERSION_1_8 23 | targetCompatibility JavaVersion.VERSION_1_8 24 | } 25 | 26 | lint { 27 | abortOnError true 28 | checkDependencies true 29 | checkTestSources true 30 | checkReleaseBuilds false 31 | } 32 | 33 | buildFeatures { 34 | dataBinding true 35 | } 36 | 37 | kotlinOptions { 38 | freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" 39 | } 40 | } 41 | 42 | dependencies { 43 | implementation project(':litr') 44 | implementation project(':litr-filters') 45 | 46 | // uncomment to experiment with ffmpeg 47 | // implementation project(':litr-ffmpeg') 48 | 49 | implementation 'androidx.appcompat:appcompat:1.2.0' 50 | implementation 'androidx.recyclerview:recyclerview:1.2.0' 51 | implementation 'androidx.multidex:multidex:2.0.1' 52 | implementation 'com.github.bumptech.glide:glide:4.14.2' 53 | implementation 'com.google.android.exoplayer:exoplayer-core:2.13.3' 54 | implementation 'com.google.android.exoplayer:exoplayer-ui:2.13.3' 55 | implementation 'com.google.android.material:material:1.3.0' 56 | implementation 'androidx.core:core-ktx:1.3.2' 57 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 58 | 59 | kapt 'com.github.bumptech.glide:compiler:4.14.2' 60 | } 61 | -------------------------------------------------------------------------------- /litr-demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | 42 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/DemoCasesAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo; 9 | 10 | import android.content.Context; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.ArrayAdapter; 15 | import android.widget.TextView; 16 | import androidx.annotation.NonNull; 17 | import androidx.annotation.Nullable; 18 | 19 | class DemoCasesAdapter extends ArrayAdapter { 20 | 21 | DemoCasesAdapter(@NonNull Context context, int resource) { 22 | super(context, resource); 23 | 24 | addAll(DemoCase.values()); 25 | } 26 | 27 | @Override 28 | public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { 29 | View view = convertView; 30 | if (view == null) { 31 | view = LayoutInflater.from(getContext()).inflate(android.R.layout.simple_list_item_1, null); 32 | } 33 | 34 | TextView textView = view.findViewById(android.R.id.text1); 35 | textView.setText(getItem(position).displayName); 36 | 37 | return textView; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/MainFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo; 9 | 10 | import android.os.Bundle; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.AdapterView; 15 | import android.widget.ArrayAdapter; 16 | import androidx.annotation.NonNull; 17 | import androidx.fragment.app.ListFragment; 18 | 19 | public class MainFragment extends ListFragment implements AdapterView.OnItemClickListener { 20 | private static final String KEY_DEMO_CASE = "demoCase"; 21 | 22 | private DemoCase demoCase; 23 | 24 | @Override 25 | public View onCreateView(LayoutInflater inflater, 26 | ViewGroup container, Bundle savedInstanceState) { 27 | View view = inflater.inflate(R.layout.fragment_main, container, false); 28 | return view; 29 | } 30 | 31 | @Override 32 | public void onActivityCreated(Bundle savedInstanceState) { 33 | super.onActivityCreated(savedInstanceState); 34 | ArrayAdapter adapter = new DemoCasesAdapter(getContext(), android.R.layout.simple_list_item_1); 35 | setListAdapter(adapter); 36 | getListView().setOnItemClickListener(this); 37 | 38 | if (savedInstanceState != null) { 39 | demoCase = (DemoCase) savedInstanceState.getSerializable(KEY_DEMO_CASE); 40 | if (demoCase != null) { 41 | startDemoCase(demoCase); 42 | } 43 | } 44 | } 45 | 46 | @Override 47 | public void onResume() { 48 | super.onResume(); 49 | 50 | demoCase = null; 51 | } 52 | 53 | @Override 54 | public void onItemClick(AdapterView adapterView, View view, int position, long id) { 55 | demoCase = (DemoCase) getListAdapter().getItem(position); 56 | startDemoCase(demoCase); 57 | } 58 | 59 | @Override 60 | public void onSaveInstanceState(@NonNull Bundle outState) { 61 | super.onSaveInstanceState(outState); 62 | 63 | outState.putSerializable(KEY_DEMO_CASE, demoCase); 64 | } 65 | 66 | private void startDemoCase(@NonNull DemoCase demoCase) { 67 | getActivity().getSupportFragmentManager().beginTransaction() 68 | .replace(R.id.fragment_container, demoCase.fragment, demoCase.fragmentTag) 69 | .addToBackStack(null) 70 | .commit(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/MediaPickerListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo; 9 | 10 | import android.net.Uri; 11 | import androidx.annotation.NonNull; 12 | 13 | public interface MediaPickerListener { 14 | 15 | void onMediaPicked(@NonNull Uri uri); 16 | } 17 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/data/AudioTrackFormat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.data; 9 | 10 | import androidx.annotation.NonNull; 11 | 12 | public class AudioTrackFormat extends MediaTrackFormat { 13 | 14 | public int channelCount; 15 | public int samplingRate; 16 | public int bitrate; 17 | public long duration; 18 | 19 | public AudioTrackFormat(int index, @NonNull String mimeType) { 20 | super(index, mimeType); 21 | } 22 | 23 | public AudioTrackFormat(@NonNull AudioTrackFormat audioTrackFormat) { 24 | super(audioTrackFormat); 25 | this.channelCount = audioTrackFormat.channelCount; 26 | this.samplingRate = audioTrackFormat.samplingRate; 27 | this.bitrate = audioTrackFormat.bitrate; 28 | this.duration = audioTrackFormat.duration; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/data/AudioVolumeConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.data; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.Nullable; 12 | import androidx.databinding.BaseObservable; 13 | import androidx.databinding.Bindable; 14 | import androidx.databinding.BindingAdapter; 15 | 16 | import com.google.android.material.slider.Slider; 17 | 18 | public class AudioVolumeConfig extends BaseObservable { 19 | 20 | public final Slider.OnChangeListener onValueChangeListener = (slider, value, fromUser) -> { 21 | this.value = slider.getValue(); 22 | }; 23 | 24 | public boolean enabled; 25 | public Float value = 1.0f; 26 | 27 | @Bindable 28 | public Boolean getEnabled() { 29 | return enabled; 30 | } 31 | 32 | @BindingAdapter(value = "onChangeListener") 33 | public static void setOnChangeListener(@NonNull Slider slider, @Nullable Slider.OnChangeListener onChangeListener) { 34 | slider.addOnChangeListener(onChangeListener); 35 | } 36 | 37 | public void setEnabled(Boolean enabled) { 38 | this.enabled = enabled; 39 | notifyChange(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/data/Converter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.data; 9 | 10 | import android.text.TextUtils; 11 | import androidx.annotation.NonNull; 12 | import androidx.databinding.InverseMethod; 13 | 14 | public class Converter { 15 | 16 | @InverseMethod("stringToInteger") 17 | public static String integerToString(int value) { 18 | return Integer.toString(value); 19 | } 20 | 21 | public static int stringToInteger(@NonNull String string) { 22 | if (!TextUtils.isEmpty(string)) { 23 | return Integer.parseInt(string); 24 | } 25 | return 0; 26 | } 27 | 28 | @InverseMethod("stringToVideoBitrate") 29 | public static String videoBitrateToString(int bitrate) { 30 | if (bitrate > 0) { 31 | return Float.toString((float) bitrate / 1000000); 32 | } 33 | return Integer.toString(bitrate); 34 | } 35 | 36 | public static int stringToVideoBitrate(@NonNull String string) { 37 | if (!TextUtils.isEmpty(string)) { 38 | return (int) (Float.parseFloat(string) * 1000000); 39 | } 40 | return 0; 41 | } 42 | 43 | @InverseMethod("stringToAudioBitrate") 44 | public static String audioBitrateToString(int bitrate) { 45 | return Integer.toString(bitrate / 1000); 46 | } 47 | 48 | public static int stringToAudioBitrate(@NonNull String string) { 49 | if (!TextUtils.isEmpty(string)) { 50 | return Integer.parseInt(string) * 1000; 51 | } 52 | return 0; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/data/GenericTrackFormat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.data; 9 | 10 | import androidx.annotation.NonNull; 11 | 12 | public class GenericTrackFormat extends MediaTrackFormat { 13 | 14 | public GenericTrackFormat(int index, @NonNull String mimeType) { 15 | super(index, mimeType); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/data/MediaTrackFormat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.data; 9 | 10 | import androidx.annotation.NonNull; 11 | 12 | public class MediaTrackFormat { 13 | 14 | public int index; 15 | public String mimeType; 16 | 17 | MediaTrackFormat(int index, @NonNull String mimeType) { 18 | this.index = index; 19 | this.mimeType = mimeType; 20 | } 21 | 22 | MediaTrackFormat(@NonNull MediaTrackFormat mediaTrackFormat) { 23 | this.index = mediaTrackFormat.index; 24 | this.mimeType = mediaTrackFormat.mimeType; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/data/SourceMedia.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.data; 9 | 10 | import android.net.Uri; 11 | import androidx.databinding.BaseObservable; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | public class SourceMedia extends BaseObservable { 17 | 18 | public Uri uri; 19 | public long size; 20 | public float duration; 21 | 22 | public List tracks = new ArrayList<>(); 23 | 24 | public boolean hasAudio() { 25 | for (MediaTrackFormat trackFormat : tracks) { 26 | if (trackFormat instanceof AudioTrackFormat) { 27 | return true; 28 | } 29 | } 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/data/TargetAudioTrack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.data; 9 | 10 | public class TargetAudioTrack extends TargetTrack { 11 | public TargetAudioTrack(int sourceTrackIndex, 12 | boolean shouldInclude, 13 | boolean shouldTranscode, 14 | AudioTrackFormat format) { 15 | super(sourceTrackIndex, shouldInclude, shouldTranscode, format); 16 | } 17 | 18 | public AudioTrackFormat getTrackFormat() { 19 | return (AudioTrackFormat) format; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/data/TargetTrack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.data; 9 | 10 | import android.net.Uri; 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.databinding.BaseObservable; 14 | 15 | public class TargetTrack extends BaseObservable { 16 | public int sourceTrackIndex; 17 | public boolean shouldInclude; 18 | public boolean shouldTranscode; 19 | public boolean shouldApplyOverlay; 20 | public Uri overlay; 21 | public MediaTrackFormat format; 22 | 23 | public TargetTrack(int sourceTrackIndex, boolean shouldInclude, boolean shouldTranscode, @NonNull MediaTrackFormat format) { 24 | this.sourceTrackIndex = sourceTrackIndex; 25 | this.shouldInclude = shouldInclude; 26 | this.shouldTranscode = shouldTranscode; 27 | this.format = format; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/data/TargetVideoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.linkedin.android.litr.demo.data; 2 | 3 | import android.view.View; 4 | import android.widget.AdapterView; 5 | 6 | import androidx.databinding.BaseObservable; 7 | 8 | public class TargetVideoConfiguration extends BaseObservable { 9 | 10 | public int rotation; 11 | 12 | public void onRotationSelected(AdapterView parent, View view, int pos, long id) { 13 | switch (pos) { 14 | case 0: 15 | // landscape 16 | rotation = 0; 17 | break; 18 | case 1: 19 | // portrait 20 | rotation = 90; 21 | break; 22 | case 2: 23 | // reverse landscape 24 | rotation = 180; 25 | break; 26 | case 3: 27 | // reverse portrait 28 | rotation = 270; 29 | break; 30 | default: 31 | // nor handled yet 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/data/TargetVideoTrack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.data; 9 | 10 | import android.view.View; 11 | import android.widget.AdapterView; 12 | 13 | public class TargetVideoTrack extends TargetTrack { 14 | 15 | public TargetVideoTrack(int sourceTrackIndex, 16 | boolean shouldInclude, 17 | boolean shouldTranscode, 18 | VideoTrackFormat format) { 19 | super(sourceTrackIndex, shouldInclude, shouldTranscode, format); 20 | } 21 | 22 | public VideoTrackFormat getTrackFormat() { 23 | return (VideoTrackFormat) format; 24 | } 25 | 26 | public void onMimeTypeSelected(AdapterView parent, View view, int pos, long id) { 27 | format.mimeType = (String) parent.getAdapter().getItem(pos); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/data/TranscodingConfigPresenter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.data; 9 | 10 | import android.content.Context; 11 | import android.widget.ImageView; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.databinding.BindingAdapter; 15 | 16 | import com.bumptech.glide.Glide; 17 | import com.linkedin.android.litr.demo.BaseTransformationFragment; 18 | import com.linkedin.android.litr.demo.MediaPickerListener; 19 | 20 | public class TranscodingConfigPresenter { 21 | 22 | private BaseTransformationFragment fragment; 23 | private TargetMedia targetMedia; 24 | 25 | public TranscodingConfigPresenter(@NonNull BaseTransformationFragment fragment, @NonNull TargetMedia targetMedia) { 26 | this.fragment = fragment; 27 | this.targetMedia = targetMedia; 28 | } 29 | 30 | public void onIncludeTrackChanged(@NonNull TargetTrack targetTrack, boolean include) { 31 | targetTrack.shouldInclude = include; 32 | targetTrack.notifyChange(); 33 | targetMedia.notifyChange(); 34 | } 35 | 36 | public void onTranscodeTrackChanged(@NonNull TargetTrack targetTrack, boolean transcode) { 37 | if (targetTrack instanceof TargetVideoTrack) { 38 | ((TargetVideoTrack) targetTrack).shouldTranscode = transcode; 39 | } else if (targetTrack instanceof TargetAudioTrack) { 40 | ((TargetAudioTrack) targetTrack).shouldTranscode = transcode; 41 | } 42 | targetTrack.notifyChange(); 43 | } 44 | 45 | public void onApplyOverlayChanged(@NonNull TargetTrack targetTrack, boolean applyOverlay) { 46 | targetTrack.shouldApplyOverlay = applyOverlay; 47 | targetTrack.notifyChange(); 48 | } 49 | 50 | public void onPickOverlayClicked(@NonNull MediaPickerListener mediaPickerListener) { 51 | fragment.pickOverlay(mediaPickerListener); 52 | } 53 | 54 | public void onPickAudioOverlayClicked(@NonNull MediaPickerListener mediaPickerListener) { 55 | fragment.pickAudio(mediaPickerListener); 56 | } 57 | 58 | public Context getContext() { 59 | return fragment.getContext(); 60 | } 61 | 62 | @BindingAdapter("overlayThumbnail") 63 | public static void loadImage(ImageView view, String imageUrl) { 64 | Glide.with(view.getContext()) 65 | .load(imageUrl) 66 | .into(view); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/data/TransformationState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.data; 9 | 10 | import androidx.annotation.IntDef; 11 | import androidx.annotation.Nullable; 12 | import androidx.databinding.BaseObservable; 13 | 14 | import java.lang.annotation.Retention; 15 | import java.lang.annotation.RetentionPolicy; 16 | 17 | public class TransformationState extends BaseObservable { 18 | 19 | public static final int MAX_PROGRESS = 100; 20 | 21 | public static final int STATE_IDLE = 0; 22 | public static final int STATE_RUNNING = 1; 23 | public static final int STATE_COMPLETED = 3; 24 | public static final int STATE_CANCELLED = 4; 25 | public static final int STATE_ERROR = 5; 26 | 27 | @Retention(RetentionPolicy.SOURCE) 28 | @IntDef({ STATE_IDLE, STATE_RUNNING, STATE_COMPLETED, STATE_CANCELLED, STATE_ERROR}) 29 | @interface State {} 30 | 31 | public String requestId; 32 | 33 | public int state; 34 | public int progress; 35 | public String stats; 36 | 37 | public TransformationState() { 38 | state = STATE_IDLE; 39 | progress = 0; 40 | stats = null; 41 | } 42 | 43 | public void setState(@State int state) { 44 | this.state = state; 45 | notifyChange(); 46 | } 47 | 48 | public void setProgress(int progress) { 49 | this.progress = progress; 50 | notifyChange(); 51 | } 52 | 53 | public void setStats(@Nullable String stats) { 54 | this.stats = stats; 55 | notifyChange(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/data/TrimConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.data; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.Nullable; 12 | import androidx.databinding.BaseObservable; 13 | import androidx.databinding.Bindable; 14 | import androidx.databinding.BindingAdapter; 15 | 16 | import com.google.android.material.slider.RangeSlider; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | public class TrimConfig extends BaseObservable { 22 | 23 | public final RangeSlider.OnChangeListener onValueChangeListener = (slider, value, fromUser) -> { 24 | range = slider.getValues(); 25 | }; 26 | 27 | public boolean enabled; 28 | public List range = new ArrayList<>(2); 29 | 30 | public TrimConfig() { 31 | range.add(0f); 32 | range.add(1f); 33 | } 34 | 35 | @Bindable 36 | public Boolean getEnabled() { 37 | return enabled; 38 | } 39 | 40 | @BindingAdapter(value = "onChangeListener") 41 | public static void setOnChangeListener(@NonNull RangeSlider rangeSlider, @Nullable RangeSlider.OnChangeListener onChangeListener) { 42 | rangeSlider.addOnChangeListener(onChangeListener); 43 | } 44 | 45 | public void setEnabled(Boolean enabled) { 46 | this.enabled = enabled; 47 | notifyChange(); 48 | } 49 | 50 | public void setTrimEnd(float trimEnd) { 51 | range.set(1, trimEnd); 52 | notifyChange(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/data/VideoFiltersPresenter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.data 9 | 10 | import android.content.Context 11 | import com.linkedin.android.litr.MediaTransformer 12 | import com.linkedin.android.litr.TransformationOptions 13 | import java.util.UUID 14 | 15 | class VideoFiltersPresenter( 16 | private val context: Context, 17 | private val mediaTransformer: MediaTransformer 18 | ) : TransformationPresenter(context, mediaTransformer) { 19 | 20 | fun applyFilter( 21 | sourceMedia: SourceMedia, 22 | targetMedia: TargetMedia, 23 | transformationState: TransformationState 24 | ) { 25 | if (targetMedia.targetFile.exists()) { 26 | targetMedia.targetFile.delete() 27 | } 28 | 29 | transformationState.requestId = UUID.randomUUID().toString() 30 | val transformationListener = MediaTransformationListener( 31 | context, 32 | transformationState.requestId, 33 | transformationState, 34 | targetMedia 35 | ) 36 | 37 | val transformationOptions = TransformationOptions.Builder() 38 | .setGranularity(MediaTransformer.GRANULARITY_DEFAULT) 39 | .setVideoFilters(listOf(targetMedia.filter)) 40 | .build() 41 | 42 | mediaTransformer.transform( 43 | transformationState.requestId, 44 | sourceMedia.uri, 45 | targetMedia.targetFile.path, 46 | null, 47 | null, 48 | transformationListener, 49 | transformationOptions 50 | ) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/data/VideoTrackFormat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.data; 9 | 10 | import androidx.annotation.NonNull; 11 | 12 | public class VideoTrackFormat extends MediaTrackFormat { 13 | 14 | public int width; 15 | public int height; 16 | public int bitrate; 17 | public int frameRate; 18 | public int keyFrameInterval; 19 | public long duration; 20 | public int rotation; 21 | 22 | public VideoTrackFormat(int index, @NonNull String mimeType) { 23 | super(index, mimeType); 24 | } 25 | 26 | public VideoTrackFormat(@NonNull VideoTrackFormat videoTrackFormat) { 27 | super(videoTrackFormat); 28 | this.width = videoTrackFormat.width; 29 | this.height = videoTrackFormat.height; 30 | this.bitrate = videoTrackFormat.bitrate; 31 | this.frameRate = videoTrackFormat.frameRate; 32 | this.keyFrameInterval = videoTrackFormat.keyFrameInterval; 33 | this.duration = videoTrackFormat.duration; 34 | this.rotation = videoTrackFormat.rotation; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/fragment/NativeMuxerCameraFragment.kt: -------------------------------------------------------------------------------- 1 | package com.linkedin.android.litr.demo.fragment 2 | 3 | import android.os.Build 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.annotation.RequiresApi 9 | 10 | @RequiresApi(Build.VERSION_CODES.M) 11 | class NativeMuxerCameraFragment: RecordCamera2Fragment() { 12 | override fun onCreateView( 13 | inflater: LayoutInflater, 14 | container: ViewGroup?, 15 | savedInstanceState: Bundle? 16 | ): View { 17 | return super.onCreateView(inflater, container, savedInstanceState).also { 18 | binding.enableNativeMuxer = true 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/fragment/NativeMuxerTranscodeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.linkedin.android.litr.demo.fragment 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.linkedin.android.litr.demo.MediaPickerListener 8 | 9 | class NativeMuxerTranscodeFragment: TranscodeVideoGlFragment(), MediaPickerListener { 10 | override fun onCreateView( 11 | inflater: LayoutInflater, 12 | container: ViewGroup?, 13 | savedInstanceState: Bundle? 14 | ): View? { 15 | return super.onCreateView(inflater, container, savedInstanceState).also { 16 | binding.enableNativeMuxer = true 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/fragment/VideoFilmStripView.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.fragment 9 | 10 | import android.content.Context 11 | import android.graphics.Bitmap 12 | import android.graphics.Canvas 13 | import android.util.AttributeSet 14 | import android.view.View 15 | import androidx.annotation.NonNull 16 | 17 | 18 | class VideoFilmStripView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) { 19 | 20 | private var bitmapList: MutableList? = null 21 | 22 | fun setFrameList(list: List) { 23 | bitmapList = list.toMutableList() 24 | invalidate() 25 | } 26 | 27 | override fun onDraw(@NonNull canvas: Canvas) { 28 | super.onDraw(canvas) 29 | var x = 0f 30 | bitmapList?.filterNotNull()?.forEach { bitmap -> 31 | canvas.drawBitmap(bitmap, x, 0f, null) 32 | x += bitmap.width 33 | } 34 | } 35 | 36 | fun setFrameAt(index: Int, bitmap: Bitmap?) { 37 | bitmapList?.set(index, bitmap) 38 | invalidate() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/view/AudioTrackViewHolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.view; 9 | 10 | import android.content.Context; 11 | import android.content.Intent; 12 | import android.net.Uri; 13 | 14 | import androidx.annotation.NonNull; 15 | import androidx.recyclerview.widget.RecyclerView; 16 | 17 | import com.linkedin.android.litr.demo.MediaPickerListener; 18 | import com.linkedin.android.litr.demo.data.AudioTrackFormat; 19 | import com.linkedin.android.litr.demo.data.TargetAudioTrack; 20 | import com.linkedin.android.litr.demo.data.TranscodingConfigPresenter; 21 | import com.linkedin.android.litr.demo.databinding.ItemAudioTrackBinding; 22 | 23 | public class AudioTrackViewHolder extends RecyclerView.ViewHolder implements MediaPickerListener { 24 | 25 | private ItemAudioTrackBinding binding; 26 | 27 | public AudioTrackViewHolder(@NonNull ItemAudioTrackBinding binding) { 28 | super(binding.getRoot()); 29 | this.binding = binding; 30 | } 31 | 32 | public void bind(@NonNull TranscodingConfigPresenter presenter, 33 | @NonNull AudioTrackFormat sourceTrackFormat, 34 | @NonNull TargetAudioTrack targetTrack) { 35 | binding.setTargetTrack(targetTrack); 36 | binding.setPresenter(presenter); 37 | binding.setAudioTrack(sourceTrackFormat); 38 | binding.executePendingBindings(); 39 | 40 | binding.buttonPickAudioOverlay.setOnClickListener( view -> 41 | presenter.onPickAudioOverlayClicked(AudioTrackViewHolder.this) 42 | ); 43 | 44 | binding.playAudioOverlay.setOnClickListener( view -> { 45 | Context context = presenter.getContext(); 46 | Uri audioOverlayUri = binding.getTargetTrack().overlay; 47 | 48 | if (context != null && audioOverlayUri != null) { 49 | Intent playIntent = new Intent(Intent.ACTION_VIEW); 50 | playIntent.setDataAndType(audioOverlayUri, context.getContentResolver().getType(audioOverlayUri)); 51 | context.startActivity(playIntent); 52 | } 53 | }); 54 | } 55 | 56 | @Override 57 | public void onMediaPicked(@NonNull Uri uri) { 58 | binding.getTargetTrack().overlay = uri; 59 | binding.getTargetTrack().notifyChange(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/view/AutoFitSurfaceView.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | * 8 | * Author: Ian Bird 9 | */ 10 | package com.linkedin.android.litr.demo.view 11 | 12 | import android.content.Context 13 | import android.util.AttributeSet 14 | import android.util.Log 15 | import android.view.SurfaceView 16 | import kotlin.math.roundToInt 17 | 18 | private const val TAG = "AutoFitSurfaceView" 19 | 20 | /** 21 | * A [SurfaceView] that can be adjusted to a specified aspect ratio and performs center-crop 22 | * transformation of input frames. 23 | */ 24 | class AutoFitSurfaceView @JvmOverloads constructor( 25 | context: Context, 26 | attrs: AttributeSet? = null, 27 | defStyle: Int = 0 28 | ) : SurfaceView(context, attrs, defStyle) { 29 | 30 | private var aspectRatio = 0f 31 | 32 | /** 33 | * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio 34 | * calculated from the parameters. 35 | * 36 | * @param width Camera resolution horizontal size 37 | * @param height Camera resolution vertical size 38 | */ 39 | fun setAspectRatio(width: Int, height: Int) { 40 | require(width > 0 && height > 0) { "Size cannot be negative" } 41 | aspectRatio = width.toFloat() / height.toFloat() 42 | holder.setFixedSize(width, height) 43 | requestLayout() 44 | } 45 | 46 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 47 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 48 | val width = MeasureSpec.getSize(widthMeasureSpec) 49 | val height = MeasureSpec.getSize(heightMeasureSpec) 50 | if (aspectRatio == 0f) { 51 | setMeasuredDimension(width, height) 52 | } else { 53 | // Performs center-crop transformation of the camera frames 54 | val newWidth: Int 55 | val newHeight: Int 56 | val actualRatio = if (width > height) aspectRatio else 1f / aspectRatio 57 | if (width < height * actualRatio) { 58 | newHeight = height 59 | newWidth = (height * actualRatio).roundToInt() 60 | } else { 61 | newWidth = width 62 | newHeight = (width / actualRatio).roundToInt() 63 | } 64 | 65 | Log.d(TAG, "Measured dimensions set: $newWidth x $newHeight") 66 | setMeasuredDimension(newWidth, newHeight) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/view/GenericTrackViewHolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.view; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.recyclerview.widget.RecyclerView; 12 | import com.linkedin.android.litr.demo.data.MediaTrackFormat; 13 | import com.linkedin.android.litr.demo.data.TargetTrack; 14 | import com.linkedin.android.litr.demo.data.TranscodingConfigPresenter; 15 | import com.linkedin.android.litr.demo.databinding.ItemGenericTrackBinding; 16 | 17 | public class GenericTrackViewHolder extends RecyclerView.ViewHolder { 18 | 19 | private ItemGenericTrackBinding binding; 20 | 21 | public GenericTrackViewHolder(@NonNull ItemGenericTrackBinding binding) { 22 | super(binding.getRoot()); 23 | this.binding = binding; 24 | } 25 | 26 | public void bind(@NonNull TranscodingConfigPresenter presenter, 27 | @NonNull MediaTrackFormat mediaTrackFormat, 28 | @NonNull TargetTrack targetTrack) { 29 | binding.setPresenter(presenter); 30 | binding.setMediaTrack(mediaTrackFormat); 31 | binding.setTargetTrack(targetTrack); 32 | binding.executePendingBindings(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /litr-demo/src/main/java/com/linkedin/android/litr/demo/view/VideoTrackViewHolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 LinkedIn Corporation 3 | * All Rights Reserved. 4 | * 5 | * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for 6 | * license information. 7 | */ 8 | package com.linkedin.android.litr.demo.view; 9 | 10 | import android.net.Uri; 11 | import android.view.View; 12 | import androidx.annotation.NonNull; 13 | import androidx.recyclerview.widget.RecyclerView; 14 | import com.linkedin.android.litr.demo.MediaPickerListener; 15 | import com.linkedin.android.litr.demo.data.TargetVideoTrack; 16 | import com.linkedin.android.litr.demo.data.TranscodingConfigPresenter; 17 | import com.linkedin.android.litr.demo.data.VideoTrackFormat; 18 | import com.linkedin.android.litr.demo.databinding.ItemVideoTrackBinding; 19 | 20 | public class VideoTrackViewHolder extends RecyclerView.ViewHolder implements MediaPickerListener { 21 | 22 | private ItemVideoTrackBinding binding; 23 | 24 | public VideoTrackViewHolder(@NonNull ItemVideoTrackBinding binding) { 25 | super(binding.getRoot()); 26 | this.binding = binding; 27 | } 28 | 29 | public void bind(@NonNull final TranscodingConfigPresenter presenter, 30 | @NonNull VideoTrackFormat videoTrackFormat, 31 | @NonNull TargetVideoTrack targetTrack) { 32 | binding.setPresenter(presenter); 33 | binding.setVideoTrack(videoTrackFormat); 34 | binding.setTargetTrack(targetTrack); 35 | binding.executePendingBindings(); 36 | 37 | binding.buttonPickVideoOverlay.setOnClickListener(new View.OnClickListener() { 38 | @Override 39 | public void onClick(View view) { 40 | presenter.onPickOverlayClicked(VideoTrackViewHolder.this); 41 | } 42 | }); 43 | } 44 | 45 | @Override 46 | public void onMediaPicked(@NonNull Uri uri) { 47 | binding.getTargetTrack().overlay = uri; 48 | binding.getTargetTrack().notifyChange(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /litr-demo/src/main/res/layout/activity_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | 14 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /litr-demo/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /litr-demo/src/main/res/layout/fragment_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /litr-demo/src/main/res/layout/fragment_video_filter_preview.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 20 | 21 | 22 | 23 | 27 | 28 | 33 | 34 | 38 | 39 | 40 | 41 | 47 | 48 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /litr-demo/src/main/res/layout/item_frame.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 18 | 19 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /litr-demo/src/main/res/layout/item_generic_track.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 16 | 17 | 20 | 21 | 22 | 23 | 27 | 28 | 33 | 34 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /litr-demo/src/main/res/layout/section_audio_volume.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 13 | 14 | 15 | 20 | 21 | 32 | 33 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /litr-demo/src/main/res/layout/section_pick_audio.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 21 | 22 |