├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── resources.properties │ │ │ ├── values-nb-rNO │ │ │ │ └── strings.xml │ │ │ ├── values-sl │ │ │ │ └── strings.xml │ │ │ ├── values │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ └── strings.xml │ │ │ ├── values-ko │ │ │ │ └── strings.xml │ │ │ ├── values-ro │ │ │ │ └── strings.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ └── ic_launcher.xml │ │ │ ├── xml │ │ │ │ ├── backup_rules.xml │ │ │ │ └── data_extraction_rules.xml │ │ │ ├── drawable │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── values-ml │ │ │ │ └── strings.xml │ │ │ ├── values-fa │ │ │ │ └── strings.xml │ │ │ ├── values-zh │ │ │ │ └── strings.xml │ │ │ ├── values-zh-rTW │ │ │ │ └── strings.xml │ │ │ ├── values-ia │ │ │ │ └── strings.xml │ │ │ ├── values-vi │ │ │ │ └── strings.xml │ │ │ ├── values-fr │ │ │ │ └── strings.xml │ │ │ ├── values-ru │ │ │ │ └── strings.xml │ │ │ ├── values-el │ │ │ │ └── strings.xml │ │ │ ├── values-pt-rBR │ │ │ │ └── strings.xml │ │ │ ├── values-bg │ │ │ │ └── strings.xml │ │ │ ├── values-de │ │ │ │ └── strings.xml │ │ │ ├── values-uk │ │ │ │ └── strings.xml │ │ │ ├── values-pt │ │ │ │ └── strings.xml │ │ │ ├── values-es │ │ │ │ └── strings.xml │ │ │ ├── values-ta │ │ │ │ └── strings.xml │ │ │ └── values-tr │ │ │ │ └── strings.xml │ │ ├── java │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── devhyper │ │ │ │ └── openvideoeditor │ │ │ │ ├── ui │ │ │ │ └── theme │ │ │ │ │ ├── Color.kt │ │ │ │ │ ├── Type.kt │ │ │ │ │ └── Theme.kt │ │ │ │ ├── misc │ │ │ │ ├── Constants.kt │ │ │ │ ├── ValidationUtils.kt │ │ │ │ └── Utils.kt │ │ │ │ ├── main │ │ │ │ ├── CustomOpenDocument.kt │ │ │ │ ├── CustomPickVisualMedia.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ └── MainScreen.kt │ │ │ │ ├── settings │ │ │ │ ├── SettingsActivity.kt │ │ │ │ ├── SettingsDataStore.kt │ │ │ │ └── SettingsScreen.kt │ │ │ │ └── videoeditor │ │ │ │ ├── VideoEditorViewModel.kt │ │ │ │ ├── CustomMuxer.java │ │ │ │ ├── VideoEditorActivity.kt │ │ │ │ ├── UserEffects.kt │ │ │ │ ├── OnVideoEditors.kt │ │ │ │ └── CustomFrameworkMuxer.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── devhyper │ │ │ └── openvideoeditor │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── io │ │ └── github │ │ └── devhyper │ │ └── openvideoeditor │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle.kts ├── metadata ├── bg │ ├── title.txt │ ├── changelogs │ │ ├── 1.txt │ │ └── 2.txt │ ├── short_description.txt │ └── full_description.txt ├── en-US │ ├── title.txt │ ├── changelogs │ │ ├── 1.txt │ │ ├── 2.txt │ │ ├── 4.txt │ │ ├── 7.txt │ │ ├── 3.txt │ │ ├── 5.txt │ │ └── 6.txt │ ├── short_description.txt │ ├── images │ │ ├── icon.png │ │ ├── featureGraphic.png │ │ ├── featureGraphicDark.png │ │ └── phoneScreenshots │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ ├── 4.png │ │ │ ├── 5.png │ │ │ ├── 6.png │ │ │ ├── 7.png │ │ │ └── 8.png │ └── full_description.txt ├── pt │ ├── title.txt │ ├── changelogs │ │ ├── 1.txt │ │ ├── 2.txt │ │ ├── 4.txt │ │ ├── 7.txt │ │ ├── 5.txt │ │ ├── 6.txt │ │ └── 3.txt │ ├── short_description.txt │ └── full_description.txt ├── zh-CN │ ├── changelogs │ │ ├── 1.txt │ │ ├── 2.txt │ │ ├── 4.txt │ │ ├── 3.txt │ │ ├── 7.txt │ │ ├── 5.txt │ │ └── 6.txt │ ├── title.txt │ ├── short_description.txt │ └── full_description.txt ├── zh-TW │ ├── changelogs │ │ ├── 1.txt │ │ └── 2.txt │ ├── title.txt │ ├── short_description.txt │ └── full_description.txt ├── de-DE │ ├── title.txt │ ├── changelogs │ │ ├── 1.txt │ │ ├── 2.txt │ │ ├── 4.txt │ │ ├── 6.txt │ │ ├── 3.txt │ │ └── 5.txt │ ├── short_description.txt │ └── full_description.txt ├── es-ES │ ├── title.txt │ ├── changelogs │ │ ├── 1.txt │ │ ├── 2.txt │ │ ├── 4.txt │ │ ├── 7.txt │ │ ├── 5.txt │ │ ├── 6.txt │ │ └── 3.txt │ ├── short_description.txt │ └── full_description.txt ├── fr-FR │ ├── title.txt │ ├── changelogs │ │ ├── 1.txt │ │ ├── 2.txt │ │ ├── 4.txt │ │ ├── 3.txt │ │ └── 5.txt │ ├── short_description.txt │ └── full_description.txt ├── pt-BR │ ├── title.txt │ ├── changelogs │ │ ├── 1.txt │ │ ├── 2.txt │ │ ├── 4.txt │ │ ├── 7.txt │ │ ├── 5.txt │ │ ├── 6.txt │ │ └── 3.txt │ ├── short_description.txt │ └── full_description.txt ├── ro │ ├── title.txt │ ├── changelogs │ │ └── 1.txt │ ├── short_description.txt │ └── full_description.txt ├── ru-RU │ ├── title.txt │ ├── changelogs │ │ ├── 1.txt │ │ ├── 2.txt │ │ ├── 4.txt │ │ ├── 7.txt │ │ ├── 3.txt │ │ ├── 5.txt │ │ └── 6.txt │ ├── short_description.txt │ └── full_description.txt ├── tr-TR │ ├── changelogs │ │ ├── 1.txt │ │ ├── 2.txt │ │ ├── 4.txt │ │ ├── 7.txt │ │ ├── 5.txt │ │ ├── 3.txt │ │ └── 6.txt │ ├── title.txt │ ├── short_description.txt │ └── full_description.txt ├── fa-IR │ ├── title.txt │ └── short_description.txt ├── uk │ ├── changelogs │ │ ├── 1.txt │ │ ├── 2.txt │ │ ├── 4.txt │ │ ├── 7.txt │ │ ├── 5.txt │ │ ├── 3.txt │ │ └── 6.txt │ ├── title.txt │ ├── short_description.txt │ └── full_description.txt └── el-GR │ └── short_description.txt ├── assets ├── IzzyOnDroid.png ├── get-it-on-fdroid.png ├── get-it-on-github.png └── google-play-badge.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── lint.xml ├── PRIVACY_POLICY.md ├── settings.gradle.kts ├── gradle.properties ├── README.md ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /metadata/bg/title.txt: -------------------------------------------------------------------------------- 1 | Open Video Editor 2 | -------------------------------------------------------------------------------- /metadata/en-US/title.txt: -------------------------------------------------------------------------------- 1 | Open Video Editor -------------------------------------------------------------------------------- /metadata/pt/title.txt: -------------------------------------------------------------------------------- 1 | Open Video Editor 2 | -------------------------------------------------------------------------------- /metadata/zh-CN/changelogs/1.txt: -------------------------------------------------------------------------------- 1 | 初始版本 2 | -------------------------------------------------------------------------------- /metadata/zh-TW/changelogs/1.txt: -------------------------------------------------------------------------------- 1 | 首次發佈 2 | -------------------------------------------------------------------------------- /metadata/zh-TW/changelogs/2.txt: -------------------------------------------------------------------------------- 1 | 小尺寸畫面調整 2 | -------------------------------------------------------------------------------- /metadata/de-DE/title.txt: -------------------------------------------------------------------------------- 1 | Open Video Editor 2 | -------------------------------------------------------------------------------- /metadata/en-US/changelogs/1.txt: -------------------------------------------------------------------------------- 1 | Initial release -------------------------------------------------------------------------------- /metadata/es-ES/title.txt: -------------------------------------------------------------------------------- 1 | Open Video Editor 2 | -------------------------------------------------------------------------------- /metadata/fr-FR/title.txt: -------------------------------------------------------------------------------- 1 | Open Video Editor 2 | -------------------------------------------------------------------------------- /metadata/pt-BR/title.txt: -------------------------------------------------------------------------------- 1 | Open Video Editor 2 | -------------------------------------------------------------------------------- /metadata/ro/title.txt: -------------------------------------------------------------------------------- 1 | Editor Video Deschis 2 | -------------------------------------------------------------------------------- /metadata/ru-RU/title.txt: -------------------------------------------------------------------------------- 1 | Open Video Editor 2 | -------------------------------------------------------------------------------- /metadata/tr-TR/changelogs/1.txt: -------------------------------------------------------------------------------- 1 | İlk sürüm 2 | -------------------------------------------------------------------------------- /metadata/tr-TR/title.txt: -------------------------------------------------------------------------------- 1 | Open Video Editor 2 | -------------------------------------------------------------------------------- /metadata/zh-CN/title.txt: -------------------------------------------------------------------------------- 1 | Open Video Editor 2 | -------------------------------------------------------------------------------- /metadata/zh-TW/title.txt: -------------------------------------------------------------------------------- 1 | Open Video Editor 2 | -------------------------------------------------------------------------------- /metadata/bg/changelogs/1.txt: -------------------------------------------------------------------------------- 1 | Първоначално издание 2 | -------------------------------------------------------------------------------- /metadata/en-US/changelogs/2.txt: -------------------------------------------------------------------------------- 1 | Small screen fixes -------------------------------------------------------------------------------- /metadata/es-ES/changelogs/1.txt: -------------------------------------------------------------------------------- 1 | Versión inicial 2 | -------------------------------------------------------------------------------- /metadata/fa-IR/title.txt: -------------------------------------------------------------------------------- 1 | بازکردن ویرایشگر ویدیو 2 | -------------------------------------------------------------------------------- /metadata/fr-FR/changelogs/1.txt: -------------------------------------------------------------------------------- 1 | Sortie initiale 2 | -------------------------------------------------------------------------------- /metadata/pt/changelogs/1.txt: -------------------------------------------------------------------------------- 1 | Lançamento inicial 2 | -------------------------------------------------------------------------------- /metadata/ro/changelogs/1.txt: -------------------------------------------------------------------------------- 1 | Lansare inițială 2 | -------------------------------------------------------------------------------- /metadata/ru-RU/changelogs/1.txt: -------------------------------------------------------------------------------- 1 | Первый выпуск 2 | -------------------------------------------------------------------------------- /metadata/uk/changelogs/1.txt: -------------------------------------------------------------------------------- 1 | Початковий випуск 2 | -------------------------------------------------------------------------------- /metadata/uk/title.txt: -------------------------------------------------------------------------------- 1 | Відкрийте відеоредактор 2 | -------------------------------------------------------------------------------- /metadata/zh-CN/changelogs/2.txt: -------------------------------------------------------------------------------- 1 | 修复了小屏幕显示相关的问题 2 | -------------------------------------------------------------------------------- /metadata/bg/changelogs/2.txt: -------------------------------------------------------------------------------- 1 | Корекции на малък екран 2 | -------------------------------------------------------------------------------- /metadata/de-DE/changelogs/1.txt: -------------------------------------------------------------------------------- 1 | Erste Veröffentlichung 2 | -------------------------------------------------------------------------------- /metadata/pt-BR/changelogs/1.txt: -------------------------------------------------------------------------------- 1 | Lançamento inicial 2 | -------------------------------------------------------------------------------- /metadata/ru-RU/changelogs/2.txt: -------------------------------------------------------------------------------- 1 | Небольшие исправления 2 | -------------------------------------------------------------------------------- /metadata/uk/changelogs/2.txt: -------------------------------------------------------------------------------- 1 | Виправлення малого екрана 2 | -------------------------------------------------------------------------------- /metadata/pt-BR/changelogs/2.txt: -------------------------------------------------------------------------------- 1 | Pequenas correções de tela 2 | -------------------------------------------------------------------------------- /metadata/pt/changelogs/2.txt: -------------------------------------------------------------------------------- 1 | Pequenas correções de ecrã 2 | -------------------------------------------------------------------------------- /metadata/tr-TR/changelogs/2.txt: -------------------------------------------------------------------------------- 1 | Küçük ekran düzeltmeleri 2 | -------------------------------------------------------------------------------- /app/src/main/res/resources.properties: -------------------------------------------------------------------------------- 1 | unqualifiedResLocale=en-US 2 | -------------------------------------------------------------------------------- /metadata/de-DE/changelogs/2.txt: -------------------------------------------------------------------------------- 1 | Korrekturen für kleine Bildschirme 2 | -------------------------------------------------------------------------------- /metadata/es-ES/changelogs/2.txt: -------------------------------------------------------------------------------- 1 | Correcciones para pantallas pequeñas 2 | -------------------------------------------------------------------------------- /metadata/fr-FR/changelogs/2.txt: -------------------------------------------------------------------------------- 1 | Petites corrections de bugs d'écran 2 | -------------------------------------------------------------------------------- /metadata/zh-CN/short_description.txt: -------------------------------------------------------------------------------- 1 | 开源的 Android 视频编辑器,使用 Media3 和 Jetpack Compose 构建。 2 | -------------------------------------------------------------------------------- /metadata/zh-TW/short_description.txt: -------------------------------------------------------------------------------- 1 | 開放原始碼 Android 影片編輯器,使用 Media3 與 Jetpack Compose 建置。 2 | -------------------------------------------------------------------------------- /metadata/fa-IR/short_description.txt: -------------------------------------------------------------------------------- 1 | ویرایشگر ویدئو متن باز، ساخته شده با Media3 و Jetpack Compose. 2 | -------------------------------------------------------------------------------- /app/src/main/res/values-nb-rNO/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/IzzyOnDroid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devhyper/open-video-editor/HEAD/assets/IzzyOnDroid.png -------------------------------------------------------------------------------- /metadata/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | Open source Android video editor, built with Media3 and Jetpack Compose. -------------------------------------------------------------------------------- /app/src/main/res/values-sl/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /metadata/bg/short_description.txt: -------------------------------------------------------------------------------- 1 | Android видео редактор с отворен код, създаден с Media3 и Jetpack Compose. 2 | -------------------------------------------------------------------------------- /assets/get-it-on-fdroid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devhyper/open-video-editor/HEAD/assets/get-it-on-fdroid.png -------------------------------------------------------------------------------- /assets/get-it-on-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devhyper/open-video-editor/HEAD/assets/get-it-on-github.png -------------------------------------------------------------------------------- /assets/google-play-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devhyper/open-video-editor/HEAD/assets/google-play-badge.png -------------------------------------------------------------------------------- /metadata/fr-FR/short_description.txt: -------------------------------------------------------------------------------- 1 | Éditeur vidéo open-source pour Android, conçu avet Media3 et Jetpack Compose. 2 | -------------------------------------------------------------------------------- /metadata/pt/short_description.txt: -------------------------------------------------------------------------------- 1 | Editor de vídeo para Android, código aberto, feito com Media3 e Jetpack Compose. 2 | -------------------------------------------------------------------------------- /metadata/ro/short_description.txt: -------------------------------------------------------------------------------- 1 | Editor video Android cu sursă deschisă, construit cu Media3 și Jetpack Compose. 2 | -------------------------------------------------------------------------------- /metadata/ru-RU/short_description.txt: -------------------------------------------------------------------------------- 1 | Видеоредактор для Android с открытым кодом на базе Media3 и Jetpack Compose. 2 | -------------------------------------------------------------------------------- /metadata/uk/short_description.txt: -------------------------------------------------------------------------------- 1 | Відеоредактор Android і відкритим вихідним кодом, створений за допомогою Media3. 2 | -------------------------------------------------------------------------------- /metadata/zh-CN/changelogs/4.txt: -------------------------------------------------------------------------------- 1 | 更新部分中文翻译 2 | 更新了依赖项 3 | 更改视频速度和帧率 4 | 在视频上添加文字 5 | 项目保存 6 | 裁剪 7 | 反转颜色 8 | 跳转到特定帧 9 | -------------------------------------------------------------------------------- /metadata/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devhyper/open-video-editor/HEAD/metadata/en-US/images/icon.png -------------------------------------------------------------------------------- /metadata/es-ES/short_description.txt: -------------------------------------------------------------------------------- 1 | Editor de vídeo Android de código abierto, desarrollo Media3 y Jetpack Compose. 2 | -------------------------------------------------------------------------------- /metadata/pt-BR/short_description.txt: -------------------------------------------------------------------------------- 1 | Editor de vídeo para Android, código aberto, feito com Media3 e Jetpack Compose. 2 | -------------------------------------------------------------------------------- /metadata/tr-TR/short_description.txt: -------------------------------------------------------------------------------- 1 | Media3 ve Jetpack Compose ile oluşturulan açık kaynak Android video düzenleyici. 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devhyper/open-video-editor/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /metadata/de-DE/short_description.txt: -------------------------------------------------------------------------------- 1 | Quelloffenes Videobearbeitungsprogramm für Android, erstellt mit Media3 und Jetpack Compose. 2 | -------------------------------------------------------------------------------- /metadata/el-GR/short_description.txt: -------------------------------------------------------------------------------- 1 | Επεξεργαστής βίντεο ανοιχτού κώδικα για Android, κατασκευασμένος με το Media3 και το Jetpack Compose. 2 | -------------------------------------------------------------------------------- /metadata/en-US/images/featureGraphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devhyper/open-video-editor/HEAD/metadata/en-US/images/featureGraphic.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /.idea 4 | .DS_Store 5 | /build 6 | /captures 7 | .externalNativeBuild 8 | .cxx 9 | local.properties 10 | -------------------------------------------------------------------------------- /metadata/en-US/images/featureGraphicDark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devhyper/open-video-editor/HEAD/metadata/en-US/images/featureGraphicDark.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devhyper/open-video-editor/HEAD/metadata/en-US/images/phoneScreenshots/1.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devhyper/open-video-editor/HEAD/metadata/en-US/images/phoneScreenshots/2.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devhyper/open-video-editor/HEAD/metadata/en-US/images/phoneScreenshots/3.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devhyper/open-video-editor/HEAD/metadata/en-US/images/phoneScreenshots/4.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devhyper/open-video-editor/HEAD/metadata/en-US/images/phoneScreenshots/5.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devhyper/open-video-editor/HEAD/metadata/en-US/images/phoneScreenshots/6.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devhyper/open-video-editor/HEAD/metadata/en-US/images/phoneScreenshots/7.png -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devhyper/open-video-editor/HEAD/metadata/en-US/images/phoneScreenshots/8.png -------------------------------------------------------------------------------- /metadata/zh-TW/full_description.txt: -------------------------------------------------------------------------------- 1 |

Open Video Editor 讓您編輯您的影片。它支援 HDR 並且允許應用篩選器。使用此應用程式您還可以裁減、縮放與旋轉甚至灰階化您的影片。 您也可以使用此應用程式從影片中匯出音訊,轉換 HDR 影片至 SDR,或者轉換成另一種格式。

2 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #00B2FF 4 | -------------------------------------------------------------------------------- /metadata/zh-CN/full_description.txt: -------------------------------------------------------------------------------- 1 |

Open Video Editor 可让您编辑视频。它支持 HDR 功能,并且允许您应用各种滤镜。您还可以用它对视频进行裁剪、缩放、旋转操作,甚至将其转换为灰度视频。您还能够利用该应用从视频中提取音频,将 HDR 视频转换为 SDR 视频,或者将视频转换为其他的格式。

2 | -------------------------------------------------------------------------------- /metadata/zh-CN/changelogs/3.txt: -------------------------------------------------------------------------------- 1 | 长按 “下一帧/上一帧” 按钮可实现重复操作 2 | -修复了切换屏幕旋转时会丢失编辑内容的问题 3 | 修复了无法选择视频的问题 4 | 在选择导出位置时,去掉文件扩展名 5 | 在“分享”菜单中添加该应用的快捷方式 6 | 修复了用户界面元素无响应或无法自适应的问题 7 | 修复了在某些屏幕尺寸下对话框按钮隐藏的问题 8 | -------------------------------------------------------------------------------- /lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /metadata/en-US/changelogs/4.txt: -------------------------------------------------------------------------------- 1 | Partial Chinese translation 2 | Update dependencies 3 | Change video speed and framerate 4 | Text on video 5 | Project saving 6 | Crop 7 | Invert colors 8 | Seek to specific frame 9 | -------------------------------------------------------------------------------- /metadata/zh-CN/changelogs/7.txt: -------------------------------------------------------------------------------- 1 | 新增功能: 2 | - 导出视频文件后扫描该文件 #85 @4831c0 3 | - 允许在应用滤镜时精确跳转至特定帧 #77 4 | - 为某些字段指定键盘类型 5 | 修复: 6 | - 修复了旋转操作导致导出取消的问题 #80 7 | 其他事项: 8 | - 同步翻译内容 9 | - 更新了依赖项 10 | -------------------------------------------------------------------------------- /metadata/tr-TR/changelogs/4.txt: -------------------------------------------------------------------------------- 1 | Kısmi Çince çeviri 2 | Bağımlılıklar güncellendi 3 | Video hızını ve kare hızını değiştir 4 | Video üzerine metin 5 | Projeyi kaydet 6 | Kırp 7 | Renkleri ters çevir 8 | Belirli bir kareye git 9 | -------------------------------------------------------------------------------- /app/src/main/res/values-ko/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 뒤로 4 | 설정 5 | 내보내기 6 | -------------------------------------------------------------------------------- /metadata/zh-CN/changelogs/5.txt: -------------------------------------------------------------------------------- 1 | 新增翻译语言: 2 | - 简体中文 3 | - 繁体中文 4 | - 法语 5 | 新增功能: 6 | - 无损剪辑 7 | - 文字效果字体自定义 8 | 修复: 9 | - 修复了尝试获取可持久化权限时应用崩溃的问题 10 | - 防止在沉浸式模式下视频尺寸被调整 11 | 其他事项: 12 | - 更新了依赖项 13 | -------------------------------------------------------------------------------- /metadata/uk/changelogs/4.txt: -------------------------------------------------------------------------------- 1 | Частковий китайський переклад 2 | Оновити залежності 3 | Зміна швидкості відео та частоти кадрів 4 | Текст на відео 5 | Збереження проекту 6 | кадрування 7 | Інвертуйте кольори 8 | Перейти до конкретного кадру 9 | -------------------------------------------------------------------------------- /metadata/pt/changelogs/4.txt: -------------------------------------------------------------------------------- 1 | Tradução parcial para chinês 2 | Atualizar dependências 3 | Alterar a velocidade e a taxa de fotogramas do vídeo 4 | Texto no vídeo 5 | Guardar projeto 6 | Cortar 7 | Inverter cores 8 | Procurar um fotograma específico 9 | -------------------------------------------------------------------------------- /metadata/es-ES/changelogs/4.txt: -------------------------------------------------------------------------------- 1 | Traducción parcial al chino 2 | Actualizar dependencias 3 | Cambiar la velocidad del video y la velocidad de fotogramas 4 | Texto en vídeo 5 | Guardar proyecto 6 | Cultivo 7 | Colores invertidos 8 | Buscar un marco específico 9 | -------------------------------------------------------------------------------- /metadata/pt-BR/changelogs/4.txt: -------------------------------------------------------------------------------- 1 | Tradução parcial em chinês 2 | Atualizão de dependências 3 | Alterar a velocidade e a taxa de quadros do vídeo 4 | Adicionar texto ao vídeo 5 | Salvar projeto 6 | Cortar 7 | Cores invertidas 8 | Procurar um quadro específico 9 | -------------------------------------------------------------------------------- /metadata/de-DE/changelogs/4.txt: -------------------------------------------------------------------------------- 1 | Teilweise chinesische Übersetzung 2 | Abhängigkeiten aktualisieren 3 | Videogeschwindigkeit und Bildwiederholrate ändern 4 | Text im Video 5 | Projekt speichern 6 | Zuschneiden 7 | Farben umkehren 8 | Suchen nach bestimmten Rahmen 9 | -------------------------------------------------------------------------------- /metadata/fr-FR/changelogs/4.txt: -------------------------------------------------------------------------------- 1 | Traduction chinoise partielle 2 | Màj des dépendances 3 | Changer la vitesse et le framerate d'une vidéo 4 | Intégrer du texte à la vidéo 5 | Sauvegarder un projet 6 | Découper 7 | Inverser les couleurs 8 | Recherche d'une frame spécifique 9 | -------------------------------------------------------------------------------- /PRIVACY_POLICY.md: -------------------------------------------------------------------------------- 1 | # Open Video Editor Privacy Policy 2 | The app does not share any information with third parties; all information is processed on the device. 3 | Videos selected by the user will be processed on the device for the purpose of app functionality (video editing). 4 | -------------------------------------------------------------------------------- /metadata/ru-RU/changelogs/4.txt: -------------------------------------------------------------------------------- 1 | Частичный перевод на китайский 2 | Обновлены зависимости 3 | Добавлены изменения скорости и частоты кадров видео 4 | Добавление текста на видео 5 | Сохранение проекта 6 | Масштабирование/обрезка 7 | Инвертирование цвета/негатив 8 | Перемещение к определённому кадру 9 | -------------------------------------------------------------------------------- /metadata/zh-CN/changelogs/6.txt: -------------------------------------------------------------------------------- 1 | 新增翻译语言: 2 | - 土耳其语 @mikropsoft 3 | - 葡萄牙语 @Diegofmar 4 | - 西班牙语 @gallegonovato 5 | 新增功能: 6 | - 支持查看意图 #73 7 | - 添加AMOLED深色主题 #66 8 | 修复: 9 | - 修复编辑时应用崩溃的问题 #70 10 | - 修复因安全异常导致的崩溃问题 11 | 其他事项: 12 | - 减小应用程序大小 13 | - 更新了依赖项 14 | -------------------------------------------------------------------------------- /metadata/en-US/changelogs/7.txt: -------------------------------------------------------------------------------- 1 | New features: 2 | - Scan video file after exporting it #85 @4831c0 3 | - Allow precise frame seeking for filters #77 4 | - Specify keyboard type for certain fields 5 | Bug fixes: 6 | - Fix rotation canceling export #80 7 | Misc: 8 | - Sync translations 9 | - Update dependencies 10 | -------------------------------------------------------------------------------- /metadata/en-US/changelogs/3.txt: -------------------------------------------------------------------------------- 1 | Touch and hold frame next/previous to repeat 2 | Fix switching screen rotation loses edits 3 | Fix unable to select a video 4 | Remove file extension when selecting export location 5 | Add the app as a shortcut in the Share menu 6 | Fix UI elements not responsive/adaptive 7 | Fix hidden dialog buttons on certain screen sizes -------------------------------------------------------------------------------- /metadata/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 |

Open Video Editor lets you edit your videos. It supports HDR and allows to apply filters. With this app you also can trim, scale, and rotate your videos or even grayscale them. It is also possible to use this app to extract audio from a video, to convert a HDR video to SDR, or to convert it to a different format.

-------------------------------------------------------------------------------- /app/src/main/res/values-ro/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Înapoi 4 | Expotează 5 | Setări 6 | Micșorează cadrul 7 | Acceptată filtrul 8 | 9 | -------------------------------------------------------------------------------- /metadata/uk/changelogs/7.txt: -------------------------------------------------------------------------------- 1 | Нові можливості: 2 | - Відскануйте відеофайл після його експорту #85 @4831c0 3 | — Дозволяє точний пошук кадру для фільтрів №77 4 | - Вкажіть тип клавіатури для певних полів 5 | Виправлення помилок: 6 | — Виправлено скасування обертання експорту #80 7 | Різне: 8 | - Синхронізація перекладів 9 | — Оновити залежності 10 | -------------------------------------------------------------------------------- /metadata/en-US/changelogs/5.txt: -------------------------------------------------------------------------------- 1 | New translations: 2 | - Chinese (Simplified) 3 | - Chinese (Traditional) 4 | - French 5 | New features: 6 | - Lossless cut 7 | - Text effect font customization 8 | Bug fixes: 9 | - Fix crash trying to take persistable permissions 10 | - Prevent video resizing on immersive mode 11 | Misc: 12 | - Update dependencies -------------------------------------------------------------------------------- /metadata/es-ES/full_description.txt: -------------------------------------------------------------------------------- 1 |

Open Video Editor te permite editar tus videos. Es compatible con HDR y permite aplicar filtros. Con esta aplicación también puedes recortar, escalar y rotar tus videos o incluso escalarlos en escala de grises. También es posible usar esta aplicación para extraer audio de un video, convertir un video HDR a SDR o convertirlo a un formato diferente.

2 | -------------------------------------------------------------------------------- /metadata/ro/full_description.txt: -------------------------------------------------------------------------------- 1 |

Open Video Editor îți dă voie să îți editezi videourile tale. Suportă HDR și permite aplicarea filtrelor. Cu această aplicație poți decupa, scala și roti videourile tale sau chiar poți adăuga scară de gri lor. Este posibil să folosești aceasta pentru a extrage sunetele dintr-un video, concertează din HDR la SDR sau concertează la un format diferit.

2 | -------------------------------------------------------------------------------- /metadata/ru-RU/changelogs/7.txt: -------------------------------------------------------------------------------- 1 | Новые возможности: 2 | - Сканирование видеофайла после его экспорта #85 @4831c0 3 | - Разрешить точный поиск кадров для фильтров #77 4 | - Указание типа клавиатуры для некоторых полей 5 | Исправления ошибок: 6 | - Исправление отмены экспорта при вращении #80 7 | Разное: 8 | - Синхронизация переводов 9 | - Обновление зависимостей 10 | -------------------------------------------------------------------------------- /metadata/tr-TR/full_description.txt: -------------------------------------------------------------------------------- 1 |

Open Video Editor videolarınızı düzenlemenizi sağlar. HDR destekler ve filtreler uygulamaya izin verir. Bu uygulama ile ayrıca videolarınızı kırpabilir, ölçeklendirebilir ve döndürebilir, hatta gri tonlama yapabilirsiniz. Bu uygulamayı bir videodan ses çıkarmak, bir HDR videoyu SDR'ye veya farklı bir biçime dönüştürmek için de kullanabilirsiniz.

2 | -------------------------------------------------------------------------------- /metadata/pt-BR/full_description.txt: -------------------------------------------------------------------------------- 1 |

Open Video Editor permite que você edite seus vídeos. Suporta HDR e permite aplicar filtros. Com este aplicativo você também pode cortar, dimensionar e girar seus vídeos ou até mesmo colocá-los em tons de cinza. Também é possível usar este aplicativo para extrair áudio de um vídeo, converter um vídeo HDR para SDR ou convertê-lo em um formato diferente.

2 | -------------------------------------------------------------------------------- /metadata/pt/full_description.txt: -------------------------------------------------------------------------------- 1 |

O Open Video Editor permite-lhe editar os seus vídeos. Suporta HDR e permite aplicar filtros. Com esta aplicação, também pode cortar, dimensionar e rodar os seus vídeos ou até mesmo em escala de cinzentos. Também é possível utilizar esta aplicação para extrair áudio de um vídeo, converter um vídeo HDR para SDR ou convertê-lo para um formato diferente.

2 | -------------------------------------------------------------------------------- /metadata/tr-TR/changelogs/7.txt: -------------------------------------------------------------------------------- 1 | Yeni özellikler: 2 | - Dışa aktardıktan sonra video dosyasını tara #85 @4831c0 3 | - Filtreler için hassas kare aramaya izin ver #77 4 | - Belirli alanlar için klavye türünü belirt 5 | Hata düzeltmeleri: 6 | - Döndürmenin dışa aktarmayı iptal etmesi düzeltildi #80 7 | Çeşitli: 8 | - Çeviriler güncellendi 9 | - Bağımlılıklar güncellendi 10 | -------------------------------------------------------------------------------- /metadata/uk/full_description.txt: -------------------------------------------------------------------------------- 1 |

Відкрити відеоредактор дозволяє редагувати відео. Він підтримує HDR і дозволяє застосовувати фільтри. За допомогою цієї програми ви також можете обрізати, масштабувати та повертати свої відео або навіть відтінки сірого. Цю програму також можна використовувати, щоб видобувати аудіо з відео, конвертувати відео HDR у SDR або конвертувати його в інший формат.

2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /metadata/en-US/changelogs/6.txt: -------------------------------------------------------------------------------- 1 | New translations: 2 | - Turkish @mikropsoft 3 | - Portuguese @Diegofmar 4 | - Spanish @gallegonovato 5 | New features: 6 | - Allow view intent #73 7 | - Add AMOLED dark theme #66 8 | Bug fixes: 9 | - Fix crash on edit #70 10 | - Fix crash on security exception 11 | Misc: 12 | - Reduce app size 13 | - Update dependencies 14 | -------------------------------------------------------------------------------- /metadata/pt-BR/changelogs/7.txt: -------------------------------------------------------------------------------- 1 | Novas funcionalidades: 2 | - Verificar arquivo de vídeo depois de o exportar #85 @4831c0 3 | - Permitir a procura precisa de frames para filtros #77 4 | - Especificar o tipo de teclado para certos campos 5 | Correções: 6 | - Correção da rotação que cancela a exportação #80 7 | Diversos: 8 | - Sincronizar traduções 9 | - Atualizar dependências 10 | -------------------------------------------------------------------------------- /metadata/pt/changelogs/7.txt: -------------------------------------------------------------------------------- 1 | Novas funcionalidades: 2 | - Verificar ficheiro de vídeo depois de o exportar #85 @4831c0 3 | - Permitir a procura precisa de fotogramas para filtros #77 4 | - Especificar o tipo de teclado para certos campos 5 | Correções: 6 | - Correção da rotação que cancela a exportação #80 7 | Diversos: 8 | - Sincronizar traduções 9 | - Atualizar dependências 10 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Purple80 = Color(0xFFD0BCFF) 6 | val PurpleGrey80 = Color(0xFFCCC2DC) 7 | val Pink80 = Color(0xFFEFB8C8) 8 | 9 | val Purple40 = Color(0xFF6650a4) 10 | val PurpleGrey40 = Color(0xFF625b71) 11 | val Pink40 = Color(0xFF7D5260) -------------------------------------------------------------------------------- /metadata/ru-RU/full_description.txt: -------------------------------------------------------------------------------- 1 |

Open Video Editor позволяет вам редактировать ваши видео. Он поддерживает HDR и позволяет применять фильтры. С помощью этого приложения вы также можете обрезать, масштабировать и вращать видео, а также превратить его в чёрно-белое. Также можно использовать это приложение для извлечения аудио из видео, конвертации HDR-видео в SDR или конвертации его в другой формат.

2 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/misc/Constants.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor.misc 2 | 3 | const val PLAYER_SEEK_BACK_INCREMENT = 5 * 1000L // 5 seconds 4 | const val PLAYER_SEEK_FORWARD_INCREMENT = 10 * 1000L // 10 seconds 5 | const val REFRESH_RATE = 100L // 100 milliseconds 6 | 7 | const val PROJECT_FILE_EXT = "ove" 8 | const val PROJECT_MIME_TYPE = "application/octet-stream" 9 | -------------------------------------------------------------------------------- /metadata/de-DE/full_description.txt: -------------------------------------------------------------------------------- 1 |

Open Video Editor kann deine Videos bearbeiten. Es unterstützt HDR und ermöglicht die Anwendung von Filtern. Mit dieser App kannst du deine Videos auch zuschneiden, skalieren und drehen oder sie sogar in Graustufen umwandeln. Es ist auch möglich, mit dieser App Audio aus einem Video zu extrahieren, ein HDR-Video in SDR zu konvertieren oder es in ein anderes Format umzuwandeln.

2 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | 16 | rootProject.name = "Open Video Editor" 17 | include(":app") 18 | -------------------------------------------------------------------------------- /metadata/bg/full_description.txt: -------------------------------------------------------------------------------- 1 |

Open Video Editor ви позволява да редактирате вашите видеоклипове. Поддържа HDR и позволява прилагане на филтри. С това приложение можете също така да изрязвате, мащабирате и завъртате видеоклиповете си или дори да ги оцветявате в сиво. Възможно е също да използвате това приложение за извличане на аудио от видео, за конвертиране на HDR видео в SDR или за конвертирането му в различен формат.

2 | -------------------------------------------------------------------------------- /metadata/tr-TR/changelogs/5.txt: -------------------------------------------------------------------------------- 1 | Yeni çeviriler: 2 | - Çince (Basitleştirilmiş) 3 | - Çince (Geleneksel) 4 | - Fransızca 5 | Yeni özellikler: 6 | - Kayıpsız kesim 7 | - Metin efekti yazı tipi özelleştirme 8 | Hata düzeltmeleri: 9 | - Kalıcı izinler almaya çalışırken oluşan çökme düzeltildi 10 | - Sürükleyici modda videoyu yeniden boyutlandırmayı önle 11 | Çeşitli: 12 | - Bağımlılıklar güncellendi 13 | -------------------------------------------------------------------------------- /metadata/es-ES/changelogs/7.txt: -------------------------------------------------------------------------------- 1 | Nuevas características: 2 | - Escanear archivo de vídeo después de exportarlo #85 @4831c0 3 | - Permitir una búsqueda precisa de cuadros para filtros #77 4 | - Especificar el tipo de teclado para campos específicos 5 | Corrección de errores: 6 | - Se corrigió la rotación que cancelaba la exportación #80 7 | Varios: 8 | - Traducciones actualizadas 9 | - Dependencias actualizadas 10 | -------------------------------------------------------------------------------- /metadata/tr-TR/changelogs/3.txt: -------------------------------------------------------------------------------- 1 | Tekrarlamak için sonraki/önceki kareye dokun ve basılı tut 2 | Ekran dönüşünü değiştirmenin düzenlemeleri kaybetmesi düzeltildi 3 | Video seçememe sorunu giderildi 4 | Dışa aktarma konumunu seçerken dosya uzantısını kaldır 5 | Uygulama Paylaş menüsüne kısayol olarak eklendi 6 | Kullanıcı arayüzü ögelerinin duyarlı/uyarlanabilir olmaması düzeltildi 7 | Belirli ekran boyutlarında gizli iletişim düğmeleri düzeltildi 8 | -------------------------------------------------------------------------------- /metadata/uk/changelogs/5.txt: -------------------------------------------------------------------------------- 1 | Нові переклади: 2 | - Китайська (спрощена) 3 | - Китайська (традиційна) 4 | - французька 5 | Нові можливості: 6 | - Вирізання без втрат 7 | — Налаштування шрифту текстового ефекту 8 | Виправлення помилок: 9 | - Виправлено збій під час спроби отримати постійні дозволи 10 | — Запобігання зміні розміру відео в режимі занурення 11 | Різне: 12 | — Оновити залежності 13 | -------------------------------------------------------------------------------- /metadata/ru-RU/changelogs/3.txt: -------------------------------------------------------------------------------- 1 | Касание и удержание элементов для повторения следующего/предыдущего кадра 2 | Исправлена проблема потери изменений при переключении экранной ориентации 3 | Исправлена невозможность выбрать видео 4 | Удалено расширение файла при выборе места экспорта 5 | Добавлен ярлык приложения в меню «Поделиться» 6 | Исправлены нереагирующие элементы в UI 7 | Исправлено скрытие кнопок в диалоговом окне на определённых размерах экрана 8 | -------------------------------------------------------------------------------- /metadata/uk/changelogs/3.txt: -------------------------------------------------------------------------------- 1 | Натисніть і утримуйте наступний/попередній кадр, щоб повторити 2 | Виправлено перемикання повороту екрана, втрачає редагування 3 | Виправлення неможливості вибрати відео 4 | Видаліть розширення файлу під час вибору місця експорту 5 | Додайте програму як ярлик у меню «Поділитися». 6 | Виправте елементи інтерфейсу користувача, які не реагують/адаптивні 7 | Виправте приховані діалогові кнопки на певних розмірах екрана 8 | -------------------------------------------------------------------------------- /metadata/pt-BR/changelogs/5.txt: -------------------------------------------------------------------------------- 1 | Novas traduções: 2 | - Chinês (Simplificado) 3 | - Chinês (Tradicional) 4 | - Francês 5 | Novas características: 6 | - Corte sem perdas 7 | - Personalização da fonte do efeito de texto 8 | Correções de bugs: 9 | - Correção de falha ao tentar obter permissões persistentes 10 | - Impedir o redimensionamento de vídeo no modo imersivo 11 | Diversos: 12 | - Atualização de dependências 13 | -------------------------------------------------------------------------------- /metadata/pt-BR/changelogs/6.txt: -------------------------------------------------------------------------------- 1 | Novas traduções: 2 | - Turco @mikropsoft 3 | - Português @Diegofmar 4 | - Espanhol @gallegonovato 5 | Novas funcionalidades: 6 | - Permitir intenção de visualização #73 7 | - Adicionar tema escuro AMOLED #66 8 | Correções: 9 | - Corrigir falha na edição #70 10 | - Corrigir falha na exceção de segurança 11 | Diversos: 12 | - Reduzir o tamanho da aplicação 13 | - Atualizar dependências 14 | -------------------------------------------------------------------------------- /metadata/pt/changelogs/5.txt: -------------------------------------------------------------------------------- 1 | Novas traduções: 2 | - Chinês (simplificado) 3 | - Chinês (tradicional) 4 | - Francês 5 | Novas características: 6 | - Corte sem perdas 7 | - Personalização da fonte do efeito de texto 8 | Correções: 9 | - Correção de falha de sistema ao tentar obter permissões persistentes 10 | - Impedir o redimensionamento de vídeo no modo imersivo 11 | Diversos: 12 | - Atualização de dependências 13 | -------------------------------------------------------------------------------- /metadata/pt/changelogs/6.txt: -------------------------------------------------------------------------------- 1 | Novas traduções: 2 | - Turco @mikropsoft 3 | - Português @Diegofmar 4 | - Espanhol @gallegonovato 5 | Novas funcionalidades: 6 | - Permitir intenção de visualização #73 7 | - Adicionar tema escuro AMOLED #66 8 | Correções: 9 | - Corrigir falha na edição #70 10 | - Corrigir falha na exceção de segurança 11 | Diversos: 12 | - Reduzir o tamanho da aplicação 13 | - Atualizar dependências 14 | -------------------------------------------------------------------------------- /metadata/uk/changelogs/6.txt: -------------------------------------------------------------------------------- 1 | Нові переклади: 2 | - турецька @mikropsoft 3 | - Португальська @Diegofmar 4 | - іспанська @gallegonovato 5 | Нові можливості: 6 | - Дозволити намір перегляду #73 7 | - Додайте темну тему AMOLED №66 8 | Виправлення помилок: 9 | — Виправлено збій у правці №70 10 | — Виправлено збій під час виключення безпеки 11 | Різне: 12 | - Зменшити розмір програми 13 | — Оновити залежності 14 | -------------------------------------------------------------------------------- /metadata/es-ES/changelogs/5.txt: -------------------------------------------------------------------------------- 1 | Nuevas traducciones: 2 | - Chino (simplificado) 3 | - Chino (tradicional) 4 | - francés 5 | Nuevas funciones: 6 | - Corte sin pérdidas 7 | - Personalización de la fuente del efecto de texto 8 | Corrección de errores: 9 | - Corrección de fallo al intentar tomar permisos persistentes 10 | - Evitar el redimensionamiento de vídeo en modo inmersivo 11 | Varios 12 | - Actualización de dependencias 13 | -------------------------------------------------------------------------------- /metadata/pt/changelogs/3.txt: -------------------------------------------------------------------------------- 1 | Toque e mantenha premido o fotograma seguinte/anterior para repetir 2 | Correção: a rotação do ecrã perde as edições 3 | Correção da impossibilidade de selecionar um vídeo 4 | Remover a extensão do ficheiro ao selecionar a localização da exportação 5 | Adicionar a aplicação como um atalho no menu Partilhar 6 | Correção de elementos da IU não responsivos/adaptativos 7 | Corrigir botões de diálogo ocultos em determinados tamanhos de ecrã 8 | -------------------------------------------------------------------------------- /metadata/tr-TR/changelogs/6.txt: -------------------------------------------------------------------------------- 1 | Yeni çeviriler: 2 | - Türkçe @mikropsoft 3 | - Portekizce @Diegofmar 4 | - İspanyolca @gallegonovato 5 | Yeni özellikler: 6 | - Görüntüleme amacına izin verildi #73 7 | - AMOLED koyu tema eklendi #66 8 | Hata düzeltmeleri: 9 | - Düzenleme sırasında çökme düzeltildi #70 10 | - Güvenlik istisnasında çökme düzeltildi 11 | Çeşitli: 12 | - Uygulama boyutu azaltıldı 13 | - Bağımlılıklar güncellendi 14 | -------------------------------------------------------------------------------- /metadata/de-DE/changelogs/6.txt: -------------------------------------------------------------------------------- 1 | Neue Übersetzungen: 2 | - Türkisch @mikropsoft 3 | - Portugiesisch @Diegofmar 4 | - Spanisch @gallegonovato 5 | Neue Funktionen: 6 | - Ansichtsabsicht zulassen #73 7 | - Dunkles AMOLED-Thema hinzufügen #66 8 | Fehlerbehebungen: 9 | - Behebt Absturz beim Bearbeiten #70 10 | - Absturz bei Sicherheitsausnahme behoben 11 | Verschiedenes: 12 | - App-Größe reduzieren 13 | - Abhängigkeiten aktualisieren 14 | -------------------------------------------------------------------------------- /metadata/fr-FR/changelogs/3.txt: -------------------------------------------------------------------------------- 1 | Appui prolongé sur Frame suivante/précédente pour répéter l'action 2 | Rectif. changement de rotation d'écran qui fait perdre les modifications 3 | Rectif. impossible de sélectionner une vidéo 4 | Retiré extension de fichier au moment di choisir le dossier d'export 5 | Ajout de l'appli en raccourci dans le menu Partage 6 | Rectif. éléments UI qui n'étaient pas "responsive" 7 | Rectif. visibilité de boutons de menu sur certaines tailles d'écran 8 | -------------------------------------------------------------------------------- /metadata/fr-FR/changelogs/5.txt: -------------------------------------------------------------------------------- 1 | Nouvelles traductions: 2 | - Chinois (Simplifié) 3 | - Chinois (Traditionnel) 4 | - Français 5 | Nouvelles fonctionnalités: 6 | - Découpage "lossless" 7 | - Personnalisation des effets de polices de texte 8 | Correction de bugs: 9 | - Rectif. crash lors de l'octroiement de permissions persistantes 10 | - Rectif. recadrage intempestif de la video en mode immersif 11 | autres: 12 | - Màj des dépendances 13 | -------------------------------------------------------------------------------- /metadata/ru-RU/changelogs/5.txt: -------------------------------------------------------------------------------- 1 | Новые переводы: 2 | ‐ Китайский (Упрощённый) 3 | ‐ Китайский (Традиционный) 4 | ‐ Французский 5 | Новые функции: 6 | ‐ Обрезка без потерь 7 | ‐ Пользовательская настройка шрифта для текстовых эффектов 8 | Исправления ошибок: 9 | ‐ Исправлена ошибка сбоя при попытке получения сохраняемых разрешений 10 | ‐ Предотвращено изменение размера видео в иммерсивном режиме 11 | Прочее: 12 | ‐ Обновление зависимостей 13 | -------------------------------------------------------------------------------- /metadata/fr-FR/full_description.txt: -------------------------------------------------------------------------------- 1 |

Open Video Editor vous permet d'éditer vos vidéos. Il supporte le format HDR et vous permet d'appliquer des filtres. Avec cette application vous pouvez aussi raccourcir des vidéos, réduire ou agrandir leur échelle, les faire pivoter ou encore les passer en nuances de gris. Il est aussi possible d'utiliser cette application pour extraire l'audio d'une vidéo, convertir une vidéo HDR en SDR, ou la convertir dans un différent format.

2 | -------------------------------------------------------------------------------- /metadata/pt-BR/changelogs/3.txt: -------------------------------------------------------------------------------- 1 | Toque e segure o quadro seguinte/anterior para repetir 2 | Correção de mudança de rotação da tela que fazia com que as edições fossem perdidas 3 | Correção de impossibilidade de selecionar um vídeo 4 | Remove a extensão do arquivo ao selecionar o local de exportação 5 | Adiciona o aplicativo como um atalho no menu Compartilhar 6 | Corrige elementos da IU que não são responsivos/adaptáveis 7 | Corrige botões de diálogo ocultos em determinados tamanhos de tela 8 | -------------------------------------------------------------------------------- /metadata/ru-RU/changelogs/6.txt: -------------------------------------------------------------------------------- 1 | Новые переводы: 2 | - Турецкий @mikropsoft 3 | - Португальский @Diegofmar 4 | - Испанский @gallegonovato 5 | Новые функции: 6 | - Разрешён просмотр в диалоговых окнах #73 7 | - Добавлена тёмная тема AMOLED #66 8 | Исправления ошибок: 9 | - Исправлен вылет при редактировании #70 10 | - Исправлена ошибка при исключении из-за безопасности 11 | Прочее: 12 | - Уменьшен размер приложения 13 | - Обновление зависимостей 14 | -------------------------------------------------------------------------------- /metadata/de-DE/changelogs/3.txt: -------------------------------------------------------------------------------- 1 | Berühre und halte das nächste/vorherige Bild zum Wiederholen 2 | Beim Umschalten der Bildschirmdrehung gehen Bearbeitungen verloren behoben 3 | Kein Video mehr auswählen können behoben 4 | Entfernen der Dateierweiterung bei der Auswahl des Exportortes 5 | Hinzufügen der App als Verknüpfung im Menü Teilen 6 | Behebung von UI-Elementen, die nicht reaktionsfähig/anpassungsfähig sind 7 | Behebung von versteckten Dialogschaltflächen auf bestimmten Bildschirmgrößen 8 | -------------------------------------------------------------------------------- /metadata/es-ES/changelogs/6.txt: -------------------------------------------------------------------------------- 1 | Nuevas traducciones: 2 | - Turco @mikropsoft 3 | - Portugués @Diegofmar 4 | - Español @gallegonovato 5 | Nuevas características: 6 | - Permitir intento de vista #73 7 | - Añadir tema oscuro AMOLED #66 8 | Corrección de errores: 9 | - Corregir el bloqueo en la edición #70 10 | - Corregir el bloqueo en la excepción de seguridad 11 | Miscelánea: 12 | - Reducir el tamaño de la aplicación 13 | - Actualizar dependencias 14 | -------------------------------------------------------------------------------- /metadata/de-DE/changelogs/5.txt: -------------------------------------------------------------------------------- 1 | Neue Übersetzungen: 2 | - Chinesisch (Vereinfacht) 3 | - Chinesisch (traditionell) 4 | - Französisch 5 | Neue Funktionen: 6 | - Verlustfreier Schnitt 7 | - Anpassung der Schriftart für Texteffekte 8 | Fehlerbehebungen: 9 | - Behebung eines Absturzes beim Versuch, persistente Berechtigungen zu übernehmen 10 | - Verhindern der Größenänderung von Videos im immersiven Modus 11 | Verschiedenes: 12 | - Aktualisieren von Abhängigkeiten 13 | -------------------------------------------------------------------------------- /app/src/test/java/io/github/devhyper/openvideoeditor/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /metadata/es-ES/changelogs/3.txt: -------------------------------------------------------------------------------- 1 | Mantenga presionado el cuadro siguiente/anterior para repetir 2 | Se corrigió el cambio de rotación de la pantalla que causaba que se perdieran las ediciones. 3 | Se corrigió la imposibilidad de seleccionar un video. 4 | Eliminar la extensión del archivo al seleccionar la ubicación de exportación 5 | Agregue la aplicación como acceso directo al menú Compartir 6 | Reparar elementos de la interfaz de usuario que no responden o no se adaptan 7 | Reparar botones de diálogo ocultos en ciertos tamaños de pantalla 8 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/main/CustomOpenDocument.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor.main 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import androidx.activity.result.contract.ActivityResultContracts 6 | 7 | class CustomOpenDocument : ActivityResultContracts.OpenDocument() { 8 | override fun createIntent(context: Context, input: Array): Intent { 9 | val intent = super.createIntent(context, input) 10 | intent.addFlags( 11 | Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION 12 | ) 13 | return intent 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/settings/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor.settings 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import io.github.devhyper.openvideoeditor.misc.setImmersiveMode 7 | import io.github.devhyper.openvideoeditor.misc.setupSystemUi 8 | 9 | class SettingsActivity : ComponentActivity() { 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | 13 | setupSystemUi() 14 | 15 | setContent { 16 | setImmersiveMode(false) 17 | SettingsScreen() 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/io/github/devhyper/openvideoeditor/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("io.github.devhyper.openvideoeditor", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | bodyLarge = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp, 15 | lineHeight = 24.sp, 16 | letterSpacing = 0.5.sp 17 | ) 18 | /* Other default text styles to override 19 | titleLarge = TextStyle( 20 | fontFamily = FontFamily.Default, 21 | fontWeight = FontWeight.Normal, 22 | fontSize = 22.sp, 23 | lineHeight = 28.sp, 24 | letterSpacing = 0.sp 25 | ), 26 | labelSmall = TextStyle( 27 | fontFamily = FontFamily.Default, 28 | fontWeight = FontWeight.Medium, 29 | fontSize = 11.sp, 30 | lineHeight = 16.sp, 31 | letterSpacing = 0.5.sp 32 | ) 33 | */ 34 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 13 | 16 | 19 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/values-ml/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | റദ്ദാക്കുക 4 | ക്രമീകരണങ്ങൾ 5 | വാചകം 6 | കയറ്റുമതി 7 | കയറ്റുമതി ചെയ്തു 8 | കയറ്റുമതി ചെയ്യുന്നു 9 | പിരിച്ചുവിടുക 10 | പിശക് 11 | പദ്ധതി 12 | വേഗത 13 | സ്വീകരിക്കുക 14 | വലിപ്പം 15 | മുൻവശത്തെ നിറം 16 | പദ്ധതി സംരക്ഷിക്കുക 17 | ഉയരം 18 | വീതി 19 | സ്ഥിരസ്ഥിതി 20 | ക്രമീകരണ വിവരം 21 | തിരിക്കുക 22 | വെടിപ്പാക്കൽ 23 | പിൻവശത്തെ നിറം 24 | തിരികെ 25 | ചേർക്കുക 26 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/main/CustomPickVisualMedia.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor.main 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import androidx.activity.result.PickVisualMediaRequest 6 | import androidx.activity.result.contract.ActivityResultContracts 7 | 8 | class CustomPickVisualMedia(private val useLegacyFilePicker: () -> Boolean) : 9 | ActivityResultContracts.PickVisualMedia() { 10 | private fun getVisualMimeType(input: VisualMediaType): String? { 11 | return when (input) { 12 | is ImageOnly -> "image/*" 13 | is VideoOnly -> "video/*" 14 | is SingleMimeType -> input.mimeType 15 | is ImageAndVideo -> null 16 | } 17 | } 18 | 19 | override fun createIntent(context: Context, input: PickVisualMediaRequest): Intent { 20 | val intent = if (useLegacyFilePicker()) { 21 | Intent(Intent.ACTION_OPEN_DOCUMENT).apply { 22 | type = getVisualMimeType(input.mediaType) 23 | 24 | if (type == null) { 25 | // ACTION_OPEN_DOCUMENT requires to set this parameter when launching the 26 | // intent with multiple mime types 27 | type = "*/*" 28 | putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*", "video/*")) 29 | } 30 | } 31 | } else { 32 | super.createIntent(context, input) 33 | } 34 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) 35 | return intent 36 | } 37 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Open Video Editor 2 | Feature graphic 3 | 4 | ## Install 5 | [Get it on GitHub](https://github.com/devhyper/open-video-editor/releases/latest) 6 | [Get it on IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/io.github.devhyper.openvideoeditor) 7 | [Get it on Google Play](https://play.google.com/store/apps/details?id=io.github.devhyper.openvideoeditor) 8 | [Get it on F-Droid](https://f-droid.org/en/packages/io.github.devhyper.openvideoeditor) 9 | 10 | ## Features 11 | - Trim 12 | - Grayscale 13 | - Resolution 14 | - Scale 15 | - Rotate 16 | 17 | ## Translations 18 | https://hosted.weblate.org/engage/open-video-editor 19 | 20 | ## Roadmap 21 | https://github.com/devhyper/open-video-editor/milestones 22 | 23 | ## Screenshots 24 |

25 | Phone screenshot 1 26 | Phone screenshot 2 27 | Phone screenshot 3 28 | Phone screenshot 4 29 | Phone screenshot 5 30 | Phone screenshot 6 31 | Phone screenshot 7 32 | Phone screenshot 8 33 |

34 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/main/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor.main 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import android.os.Bundle 6 | import androidx.activity.ComponentActivity 7 | import androidx.activity.compose.setContent 8 | import androidx.activity.result.ActivityResultLauncher 9 | import androidx.activity.result.PickVisualMediaRequest 10 | import io.github.devhyper.openvideoeditor.misc.setImmersiveMode 11 | import io.github.devhyper.openvideoeditor.misc.setupSystemUi 12 | import io.github.devhyper.openvideoeditor.settings.SettingsDataStore 13 | import io.github.devhyper.openvideoeditor.videoeditor.VideoEditorActivity 14 | 15 | class MainActivity : ComponentActivity() { 16 | private lateinit var pickMedia: ActivityResultLauncher 17 | private lateinit var pickProject: ActivityResultLauncher> 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | 21 | setupSystemUi() 22 | 23 | val dataStore = SettingsDataStore(this) 24 | pickMedia = 25 | registerForActivityResult(CustomPickVisualMedia { dataStore.getLegacyFilePickerBlocking() }) { uri -> 26 | if (uri != null) { 27 | launchVideoEditor(uri) 28 | } 29 | } 30 | pickProject = registerForActivityResult( 31 | CustomOpenDocument() 32 | ) { uri -> 33 | if (uri != null) { 34 | launchVideoEditor(uri) 35 | } 36 | } 37 | 38 | setContent { 39 | setImmersiveMode(false) 40 | MainScreen(pickMedia, pickProject) 41 | } 42 | } 43 | 44 | private fun launchVideoEditor(uri: Uri) { 45 | val intent = Intent(this, VideoEditorActivity::class.java) 46 | intent.action = Intent.ACTION_EDIT 47 | intent.data = uri 48 | startActivity(intent) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/misc/ValidationUtils.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor.misc 2 | 3 | fun validateInt(string: String): String { 4 | if (string.toIntOrNull() == null) { 5 | return "Input must be a valid integer" 6 | } 7 | return "" 8 | } 9 | 10 | fun validateIntAndNonzero(string: String): String { 11 | val int = string.toIntOrNull() 12 | if (int == null) { 13 | return "Input must be a valid integer" 14 | } else if (int == 0) { 15 | return "Input must be nonzero" 16 | } 17 | return "" 18 | } 19 | 20 | fun validateUInt(string: String): String { 21 | if (string.toUIntOrNull() == null) { 22 | return "Input must be a valid unsigned integer" 23 | } 24 | return "" 25 | } 26 | 27 | fun validateUIntAndNonzero(string: String): String { 28 | val uint = string.toUIntOrNull() 29 | if (uint == null) { 30 | return "Input must be a valid unsigned integer" 31 | } else if (uint == 0U) { 32 | return "Input must be nonzero" 33 | } 34 | return "" 35 | } 36 | 37 | fun validateFloat(string: String): String { 38 | if (string.toFloatOrNull() == null) { 39 | return "Input must be a valid float" 40 | } 41 | return "" 42 | } 43 | 44 | fun validateFloatAndNonzero(string: String): String { 45 | val float = string.toFloatOrNull() 46 | if (float == null) { 47 | return "Input must be a valid float" 48 | } else if (float == 0F) { 49 | return "Input must be nonzero" 50 | } 51 | return "" 52 | } 53 | 54 | fun validateUFloat(string: String): String { 55 | val float = string.toFloatOrNull() 56 | if (float == null || float < 0) { 57 | return "Input must be a valid unsigned float" 58 | } 59 | return "" 60 | } 61 | 62 | fun validateUFloatAndNonzero(string: String): String { 63 | val float = string.toFloatOrNull() 64 | if (float == null || float < 0) { 65 | return "Input must be a valid unsigned float" 66 | } else if (float == 0F) { 67 | return "Input must be nonzero" 68 | } 69 | return "" 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/res/values-fa/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | کاستن فریم 4 | افزونش فریم 5 | پذیرش فیلتر 6 | کاهش فیلتر 7 | پاک‌کردن فیلتر 8 | تراش 9 | وازدن 10 | رسانه برای برون‌برد 11 | سردگ ویدیو 12 | برون‌برده شد 13 | نادیده گرفتن 14 | پیرنگ 15 | لایه‌های ویدیو 16 | پخش/ایست 17 | نویسه 18 | نشاندن 19 | ذخیره‌ی پیرنگ 20 | فریم‌ها 21 | فریم نو 22 | ویدیو 23 | فلس خاکستری 24 | واگرداندن رنگ‌ها 25 | کیفیت 26 | پهنا 27 | بلندا 28 | چرخاندن 29 | درجه‌ها 30 | برش 31 | ایرنگ FFmpeg 32 | کلک 33 | بازگشت 34 | پوسته‌ی مشکی ناب 35 | برون‌برد 36 | پیکربندی‌ها 37 | نمادک لایه 38 | فیلتر‌های ویدیو 39 | افزودن 40 | سردگ صدا 41 | سان HDR 42 | در حال برون‌بردن 43 | گزیدن پرونده‌ای برای ویرایش 44 | ایرنگ 45 | ۱۰ ثانیه پس‌راندن 46 | ۵ ثانیه پیش‌راندن 47 | نرخ‌فریم 48 | تندی 49 | پوسته 50 | بکارگیری پرونده گزین کهنه 51 | رنگ پس‌زمینه 52 | اندازه 53 | پذیرش 54 | رنگ گزین 55 | پیش‌گزیده 56 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 11 | 12 | 15 | 16 | 17 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 返回 4 | 导出 5 | 设置 6 | 上一帧 7 | 下一帧 8 | 应用过滤器 9 | 减少过滤器 10 | 打开图层面板 11 | 打开过滤器面板 12 | 图层图标 13 | 删除过滤器 14 | 视频过滤器 15 | 剪辑 16 | 取消 17 | 添加 18 | 导出内容 19 | HDR模式 20 | 音频类型 21 | 视频类型 22 | 已导出 23 | 导出中 24 | 关闭 25 | 错误 26 | 选择一个文件以开始编辑 27 | 项目 28 | 视频图层 29 | 向前10秒 30 | 播放/暂停 31 | 回放5秒 32 | 更多垂直选项 33 | 帧率 34 | 速度 35 | 主题 36 | 使用旧版文件选择器 37 | 背景色 38 | 前景色 39 | 文本 40 | 应用 41 | 大小 42 | 颜色选择器 43 | 接受 44 | 保存项目 45 | 使用UI瀑布效果 46 | 47 | 目标 48 | 视频 49 | 灰度 50 | 反转颜色 51 | 分辨率 52 | 缩放 53 | 旋转 54 | 裁剪 55 | 输入的帧数必须小于等于 56 | 57 | 58 | 布局 59 | 角度 60 | 帧率必须比原视频的帧率低 61 | 开启无损剪切将只导出剪辑。 62 | FFmpeg 错误 63 | 默认 64 | 字体 65 | 无损剪切 66 | 设置信息 67 | AMOLED 深色主题 68 | 权限被拒绝,请授予视频权限 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 圖層圖示 4 | 前一影格 5 | 後一影格 6 | 應用篩選器 7 | 減少篩選器 8 | 開啟圖層面板 9 | 開啟篩選器面板 10 | HDR 模式 11 | 音訊編碼類型 12 | 視訊編碼類型 13 | 移除篩選器 14 | 匯出媒體 15 | 剪輯 16 | 已匯出 17 | 關閉 18 | 正在匯出 19 | 選擇一個檔案進行編輯 20 | 專案 21 | 播放/暫停 22 | 前進 10 秒 23 | 更多的垂直選項 24 | 影格速率 25 | 主題 26 | 文字 27 | 應用 28 | 接受 29 | 儲存專案 30 | 影格 31 | 大小 32 | 灰度 33 | 新影格 34 | 反轉色彩 35 | 解析度 36 | 寬度 37 | 高度 38 | 佈局 39 | 縮放 40 | 旋轉 41 | 角度 42 | 裁切 43 | 影格速率必須小於原始視訊的影格速率 44 | 設定資訊 45 | 無損裁剪 46 | FFmpeg 錯誤 47 | 啟用無損裁剪只能匯出剪輯。 48 | 返回 49 | 匯出 50 | 設定 51 | 取消 52 | 新增 53 | 錯誤 54 | 視訊圖層 55 | 重播 5 秒 56 | 速度 57 | 使用舊版檔案選擇器 58 | 背景色彩 59 | 前景色彩 60 | 色彩選擇器 61 | 影片 62 | 視訊篩選器 63 | 輸入必須小於或等於 64 | 預設 65 | 字型 66 | AMOLED 深色模式 67 | 權限被拒絕,授予視訊權限 68 | 使用介面層疊效果 69 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor.ui.theme 2 | 3 | import android.app.Activity 4 | import android.os.Build 5 | import androidx.compose.foundation.isSystemInDarkTheme 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.darkColorScheme 8 | import androidx.compose.material3.dynamicDarkColorScheme 9 | import androidx.compose.material3.dynamicLightColorScheme 10 | import androidx.compose.material3.lightColorScheme 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.SideEffect 13 | import androidx.compose.runtime.collectAsState 14 | import androidx.compose.runtime.getValue 15 | import androidx.compose.ui.graphics.Color 16 | import androidx.compose.ui.graphics.toArgb 17 | import androidx.compose.ui.platform.LocalContext 18 | import androidx.compose.ui.platform.LocalView 19 | import androidx.core.view.WindowCompat 20 | import io.github.devhyper.openvideoeditor.settings.SettingsDataStore 21 | 22 | private val DarkColorScheme = darkColorScheme( 23 | primary = Purple80, 24 | secondary = PurpleGrey80, 25 | tertiary = Pink80 26 | ) 27 | 28 | private val LightColorScheme = lightColorScheme( 29 | primary = Purple40, 30 | secondary = PurpleGrey40, 31 | tertiary = Pink40 32 | 33 | /* Other default colors to override 34 | background = Color(0xFFFFFBFE), 35 | surface = Color(0xFFFFFBFE), 36 | onPrimary = Color.White, 37 | onSecondary = Color.White, 38 | onTertiary = Color.White, 39 | onBackground = Color(0xFF1C1B1F), 40 | onSurface = Color(0xFF1C1B1F), 41 | */ 42 | ) 43 | 44 | @Composable 45 | fun OpenVideoEditorTheme( 46 | forceDarkTheme: Boolean = false, 47 | forceBlackStatusBar: Boolean = false, 48 | // Dynamic color is available on Android 12+ 49 | dynamicColor: Boolean = true, 50 | content: @Composable () -> Unit 51 | ) { 52 | val dataStore = SettingsDataStore(LocalContext.current) 53 | val theme by dataStore.getThemeAsync().collectAsState(dataStore.getThemeBlocking()) 54 | val amoled by dataStore.getAmoledAsync().collectAsState(dataStore.getAmoledBlocking()) 55 | 56 | val darkTheme = if (forceDarkTheme) { 57 | true 58 | } else { 59 | when (theme) { 60 | "Light" -> false 61 | "Dark" -> true 62 | else -> isSystemInDarkTheme() 63 | } 64 | } 65 | 66 | var colorScheme = when { 67 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 68 | val context = LocalContext.current 69 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 70 | } 71 | 72 | darkTheme -> DarkColorScheme 73 | else -> LightColorScheme 74 | } 75 | if (darkTheme && amoled) { 76 | colorScheme = colorScheme.copy(background = Color.Black, surface = Color.Black) 77 | } 78 | 79 | val view = LocalView.current 80 | if (!view.isInEditMode) { 81 | SideEffect { 82 | val window = (view.context as Activity).window 83 | window.statusBarColor = 84 | if (forceBlackStatusBar) Color.Black.toArgb() else colorScheme.background.toArgb() 85 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme 86 | } 87 | } 88 | 89 | MaterialTheme( 90 | colorScheme = colorScheme, 91 | typography = Typography, 92 | content = content 93 | ) 94 | } -------------------------------------------------------------------------------- /app/src/main/res/values-ia/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Avantiar 10 secundas 4 | Reproducer/Pausar 5 | Velocitate 6 | Usar le selector ancian de files 7 | Definir 8 | Salveguardar le projecto 9 | Predefinite 10 | Cancellar 11 | Adder 12 | Medio a exportar 13 | Modo HDR 14 | Typo de audio 15 | Exportate 16 | Typo de video 17 | In exportation 18 | Dimitter 19 | Error 20 | Selige un file a modificar 21 | Projecto 22 | Plus de optiones vertical 23 | Thema 24 | Color de fundo 25 | Color de prime plano 26 | Texto 27 | Dimension 28 | Selector de color 29 | Acceptar 30 | Video 31 | Scala de gris 32 | Inverter le colores 33 | Resolution 34 | Disposition 35 | Scala 36 | Taliar 37 | Error de FFmpeg 38 | Exportar 39 | Parametros 40 | Acceptar le filtro 41 | Remover le filtro 42 | Filtros de video 43 | Accurtar 44 | Retornar 45 | Aperir le tiratorio de filtros 46 | Declinar le filtro 47 | Frequentia de photogrammas per secunda 48 | Photogrammas 49 | Le entrata debe esser minus que o equal a 50 | Grados 51 | Information del configuration 52 | Taliar sin perdita 53 | Activar le taliar sin perdita solmente exportara le sectiones taliate. 54 | Typo de litteras 55 | Le frequentia de photogrammas debe esser inferior al frequentia de photogrammas del video original 56 | Nove photogramma 57 | Thema obscur AMOLED 58 | Permission refusate, concede le permissiones de video 59 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/settings/SettingsDataStore.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor.settings 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.preferences.core.Preferences 6 | import androidx.datastore.preferences.core.booleanPreferencesKey 7 | import androidx.datastore.preferences.core.edit 8 | import androidx.datastore.preferences.core.stringPreferencesKey 9 | import androidx.datastore.preferences.preferencesDataStore 10 | import kotlinx.coroutines.flow.Flow 11 | import kotlinx.coroutines.flow.first 12 | import kotlinx.coroutines.flow.map 13 | import kotlinx.coroutines.runBlocking 14 | 15 | class SettingsDataStore(private val context: Context) { 16 | 17 | companion object { 18 | private val Context.dataStore: DataStore by preferencesDataStore("settings") 19 | val THEME = stringPreferencesKey("theme") 20 | val LEGACY_FILE_PICKER = booleanPreferencesKey("legacy_file_picker") 21 | val UI_CASCADING_EFFECT = booleanPreferencesKey("ui_cascading_effect") 22 | val AMOLED_DARK_THEME = booleanPreferencesKey("amoled_dark_theme") 23 | } 24 | 25 | fun getThemeBlocking(): String { 26 | return runBlocking { 27 | val preferences = context.dataStore.data.first() 28 | preferences[THEME] ?: "System" 29 | } 30 | } 31 | 32 | fun getThemeAsync(): Flow { 33 | return context.dataStore.data 34 | .map { preferences -> 35 | preferences[THEME] ?: "System" 36 | } 37 | } 38 | 39 | suspend fun setTheme(value: String) { 40 | context.dataStore.edit { preferences -> 41 | preferences[THEME] = value 42 | } 43 | } 44 | 45 | fun getAmoledBlocking(): Boolean { 46 | return runBlocking { 47 | val preferences = context.dataStore.data.first() 48 | preferences[AMOLED_DARK_THEME] ?: false 49 | } 50 | } 51 | 52 | fun getAmoledAsync(): Flow { 53 | return context.dataStore.data 54 | .map { preferences -> 55 | preferences[AMOLED_DARK_THEME] ?: false 56 | } 57 | } 58 | 59 | suspend fun setAmoled(value: Boolean) { 60 | context.dataStore.edit { preferences -> 61 | preferences[AMOLED_DARK_THEME] = value 62 | } 63 | } 64 | 65 | fun getLegacyFilePickerBlocking(): Boolean { 66 | return runBlocking { 67 | val preferences = context.dataStore.data.first() 68 | preferences[LEGACY_FILE_PICKER] ?: false 69 | } 70 | } 71 | 72 | suspend fun setLegacyFilePicker(value: Boolean) { 73 | context.dataStore.edit { preferences -> 74 | preferences[LEGACY_FILE_PICKER] = value 75 | } 76 | } 77 | 78 | fun getUiCascadingEffectBlocking(): Boolean { 79 | return runBlocking { 80 | val preferences = context.dataStore.data.first() 81 | preferences[UI_CASCADING_EFFECT] ?: false 82 | } 83 | } 84 | 85 | fun getUiCascadingEffectAsync(): Flow { 86 | return context.dataStore.data 87 | .map { preferences -> 88 | preferences[UI_CASCADING_EFFECT] ?: false 89 | } 90 | } 91 | 92 | suspend fun setUiCascadingEffect(value: Boolean) { 93 | context.dataStore.edit { preferences -> 94 | preferences[UI_CASCADING_EFFECT] = value 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | android { 7 | namespace = "io.github.devhyper.openvideoeditor" 8 | compileSdk = 34 9 | 10 | defaultConfig { 11 | applicationId = "io.github.devhyper.openvideoeditor" 12 | minSdk = 26 13 | targetSdk = 34 14 | versionCode = 7 15 | versionName = "1.1.3" 16 | 17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 18 | vectorDrawables { 19 | useSupportLibrary = true 20 | } 21 | } 22 | 23 | buildTypes { 24 | release { 25 | isDebuggable = false 26 | isMinifyEnabled = true 27 | isShrinkResources = true 28 | proguardFiles( 29 | getDefaultProguardFile("proguard-android-optimize.txt"), 30 | "proguard-rules.pro" 31 | ) 32 | } 33 | debug { 34 | isDebuggable = true 35 | isMinifyEnabled = false 36 | isShrinkResources = false 37 | applicationIdSuffix = ".debug" 38 | } 39 | } 40 | compileOptions { 41 | sourceCompatibility = JavaVersion.VERSION_1_8 42 | targetCompatibility = JavaVersion.VERSION_1_8 43 | } 44 | kotlinOptions { 45 | jvmTarget = "1.8" 46 | } 47 | buildFeatures { 48 | compose = true 49 | } 50 | composeOptions { 51 | kotlinCompilerExtensionVersion = "1.4.3" 52 | } 53 | packaging { 54 | resources { 55 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 56 | } 57 | } 58 | androidResources { 59 | generateLocaleConfig = true 60 | } 61 | } 62 | 63 | dependencies { 64 | implementation("androidx.core:core-ktx:1.13.1") 65 | implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.4") 66 | implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.4") 67 | implementation("androidx.activity:activity-compose:1.9.1") 68 | implementation(platform("androidx.compose:compose-bom:2024.08.00")) 69 | implementation("androidx.compose.ui:ui") 70 | implementation("androidx.compose.ui:ui-graphics") 71 | implementation("androidx.compose.ui:ui-tooling-preview") 72 | implementation("androidx.compose.material3:material3") 73 | implementation("androidx.compose.material:material-icons-core") 74 | implementation("androidx.compose.material:material-icons-extended") 75 | implementation("androidx.media3:media3-exoplayer:1.4.1") 76 | implementation("androidx.media3:media3-transformer:1.4.1") 77 | implementation("androidx.media3:media3-effect:1.4.1") 78 | implementation("androidx.media3:media3-common:1.4.1") 79 | implementation("androidx.media3:media3-muxer:1.4.1") 80 | implementation("androidx.navigation:navigation-compose:2.7.7") 81 | implementation("androidx.datastore:datastore-preferences:1.1.1") 82 | implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7") 83 | implementation("com.godaddy.android.colorpicker:compose-color-picker:0.7.0") 84 | implementation("com.godaddy.android.colorpicker:compose-color-picker-android:0.7.0") 85 | implementation("com.arthenica:ffmpeg-kit-min:6.0-2") 86 | testImplementation("junit:junit:4.13.2") 87 | androidTestImplementation("androidx.test.ext:junit:1.2.1") 88 | androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") 89 | androidTestImplementation(platform("androidx.compose:compose-bom:2024.08.00")) 90 | androidTestImplementation("androidx.compose.ui:ui-test-junit4") 91 | debugImplementation("androidx.compose.ui:ui-tooling") 92 | debugImplementation("androidx.compose.ui:ui-test-manifest") 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/videoeditor/VideoEditorViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor.videoeditor 2 | 3 | import androidx.lifecycle.ViewModel 4 | import kotlinx.collections.immutable.PersistentList 5 | import kotlinx.collections.immutable.persistentListOf 6 | import kotlinx.coroutines.flow.MutableStateFlow 7 | import kotlinx.coroutines.flow.StateFlow 8 | import kotlinx.coroutines.flow.asStateFlow 9 | import kotlinx.coroutines.flow.update 10 | 11 | class VideoEditorViewModel : ViewModel() { 12 | val transformManager = TransformManager() 13 | 14 | private val _outputPath = MutableStateFlow("") 15 | val outputPath: StateFlow = _outputPath.asStateFlow() 16 | 17 | private val _projectOutputPath = MutableStateFlow("") 18 | val projectOutputPath: StateFlow = _projectOutputPath.asStateFlow() 19 | 20 | private val _controlsVisible = MutableStateFlow(true) 21 | val controlsVisible: StateFlow = _controlsVisible.asStateFlow() 22 | 23 | private val _projectSavingSupported = MutableStateFlow(true) 24 | val projectSavingSupported: StateFlow = _projectSavingSupported.asStateFlow() 25 | 26 | private val _filterDurationEditorEnabled = MutableStateFlow(false) 27 | val filterDurationEditorEnabled = _filterDurationEditorEnabled.asStateFlow() 28 | 29 | private val _filterDurationCallback = MutableStateFlow<(LongRange) -> Unit> {} 30 | val filterDurationCallback: StateFlow<(LongRange) -> Unit> = 31 | _filterDurationCallback.asStateFlow() 32 | 33 | private val _filterDurationEditorSliderPosition = MutableStateFlow(0f..0f) 34 | val filterDurationEditorSliderPosition: StateFlow> = 35 | _filterDurationEditorSliderPosition.asStateFlow() 36 | 37 | private val _startFilterSelected = MutableStateFlow(true) 38 | val startFilterSelected: StateFlow = _startFilterSelected.asStateFlow() 39 | 40 | private val _filterDialogArgs = MutableStateFlow>( 41 | persistentListOf() 42 | ) 43 | val filterDialogArgs: StateFlow> = 44 | _filterDialogArgs.asStateFlow() 45 | 46 | private val _currentEditingEffect = MutableStateFlow(null) 47 | val currentEditingEffect: StateFlow = 48 | _currentEditingEffect.asStateFlow() 49 | 50 | fun setOutputPath(path: String) { 51 | _outputPath.update { path } 52 | } 53 | 54 | fun setProjectOutputPath(path: String) { 55 | _projectOutputPath.update { path } 56 | } 57 | 58 | fun setControlsVisible(value: Boolean) { 59 | _controlsVisible.update { value } 60 | } 61 | 62 | fun setProjectSavingSupported(value: Boolean) { 63 | _projectSavingSupported.update { value } 64 | } 65 | 66 | fun setFilterDurationEditorEnabled(value: Boolean) { 67 | _filterDurationEditorEnabled.update { value } 68 | } 69 | 70 | fun setFilterDurationCallback(value: (LongRange) -> Unit) { 71 | _filterDurationCallback.update { value } 72 | } 73 | 74 | fun setFilterDurationEditorSliderPosition(value: ClosedFloatingPointRange) { 75 | _filterDurationEditorSliderPosition.update { value } 76 | } 77 | 78 | fun setStartFilterSelected(value: Boolean) { 79 | _startFilterSelected.update { value } 80 | } 81 | 82 | fun setFilterDialogArgs(value: PersistentList) { 83 | _filterDialogArgs.update { value } 84 | } 85 | 86 | fun setCurrentEditingEffect(value: OnVideoUserEffect?) { 87 | _currentEditingEffect.update { value } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/videoeditor/CustomMuxer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.devhyper.openvideoeditor.videoeditor; 17 | 18 | import android.media.MediaCodec.BufferInfo; 19 | 20 | import androidx.media3.common.C; 21 | import androidx.media3.common.Format; 22 | import androidx.media3.common.Metadata; 23 | import androidx.media3.common.util.UnstableApi; 24 | import androidx.media3.muxer.Muxer; 25 | 26 | import com.google.common.collect.ImmutableList; 27 | 28 | import java.io.FileDescriptor; 29 | import java.nio.ByteBuffer; 30 | 31 | @UnstableApi 32 | public final class CustomMuxer implements Muxer { 33 | 34 | /** 35 | * A {@link Muxer.Factory} for {@link CustomMuxer}. 36 | */ 37 | public static final class Factory implements Muxer.Factory { 38 | private final Muxer.Factory muxerFactory; 39 | 40 | /** 41 | * Creates an instance with {@code videoDurationMs} set to {@link C#TIME_UNSET}. 42 | */ 43 | public Factory() { 44 | this(null, /* videoDurationMs= */ C.TIME_UNSET); 45 | } 46 | 47 | public Factory(FileDescriptor fd) { 48 | this(fd, /* videoDurationMs= */ C.TIME_UNSET); 49 | } 50 | 51 | /** 52 | * Creates an instance. 53 | * 54 | * @param videoDurationMs The duration of the video track (in milliseconds) to enforce in the 55 | * output, or {@link C#TIME_UNSET} to not enforce. Only applicable when a video track is 56 | * {@linkplain #addTrack(Format) added}. 57 | */ 58 | public Factory(FileDescriptor fd, long videoDurationMs) { 59 | this.muxerFactory = new CustomFrameworkMuxer.Factory(fd, videoDurationMs); 60 | } 61 | 62 | @Override 63 | public Muxer create(String path) throws MuxerException { 64 | return new CustomMuxer(muxerFactory.create(path)); 65 | } 66 | 67 | @Override 68 | public ImmutableList getSupportedSampleMimeTypes(@C.TrackType int trackType) { 69 | return muxerFactory.getSupportedSampleMimeTypes(trackType); 70 | } 71 | } 72 | 73 | private final Muxer muxer; 74 | 75 | private CustomMuxer(Muxer muxer) { 76 | this.muxer = muxer; 77 | } 78 | 79 | @Override 80 | public TrackToken addTrack(Format format) throws MuxerException { 81 | return muxer.addTrack(format); 82 | } 83 | 84 | @Override 85 | public void writeSampleData(TrackToken trackToken, ByteBuffer byteBuffer, BufferInfo bufferInfo) 86 | throws MuxerException { 87 | muxer.writeSampleData(trackToken, byteBuffer, bufferInfo); 88 | } 89 | 90 | @Override 91 | public void addMetadataEntry(Metadata.Entry metadataEntry) { 92 | muxer.addMetadataEntry(metadataEntry); 93 | } 94 | 95 | @Override 96 | public void close() throws MuxerException { 97 | muxer.close(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/res/values-vi/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Quay lại 4 | Xuất 5 | Cài đặt 6 | Chấp nhận bộ lọc 7 | Từ chối bộ lọc 8 | Mở khay lớp 9 | Mở khay bộ lọc 10 | Biểu tượng lớp 11 | Xóa bộ lọc 12 | Bộ lọc video 13 | Cắt 14 | Hủy 15 | Thêm 16 | Xuất media 17 | Chế độ HDR 18 | Loại âm thanh 19 | Loại video 20 | Lỗi 21 | Chọn tệp để chỉnh sửa 22 | Dự án 23 | Chơi/tạm dừng 24 | Lặp lại 5 giây 25 | Thêm tùy chọn dọc 26 | Tần số khung hình 27 | Tốc độ 28 | Chủ đề 29 | Dùng trình chọn tệp cũ 30 | Màu nền trước 31 | Màu nền sau 32 | Văn bản 33 | Đặt 34 | Kích thước 35 | Chọn màu 36 | Lưu dự án 37 | Giảm khung 38 | Tăng khung 39 | Loại bỏ 40 | Dùng hiệu ứng xếp chồng UI 41 | Khung hình 42 | Khung hình mới 43 | Dữ liệu vào không được lớn hơn 44 | Video 45 | Đảo ngược màu 46 | Độ phân giải 47 | Chiều rộng 48 | Chiều cao 49 | Bài trí 50 | Tỉ lệ 51 | Xoay 52 | Độ 53 | Xén 54 | Tần số khung hình không thể vượt quá tần số của video gốc 55 | Thông tin cài đặt 56 | Lỗi FFmpeg 57 | Mặc định 58 | Phông chữ 59 | Chế độ ban đêm AMOLED 60 | Quyền bị từ chối, hãy cấp quyền truy cập video 61 | Đen trắng 62 | Cắt không mất dữ liệu 63 | Kích hoạt cắt không mất dữ liệu sẽ chỉ xuất phần \"cắt\" (không xén). 64 | Đã xuất 65 | Đang xuất 66 | Các lớp của video 67 | Tiến 10 giây 68 | Chấp nhận 69 | -------------------------------------------------------------------------------- /app/src/main/res/values-fr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Retour 4 | Exporter 5 | Paramètres 6 | Frame précédente 7 | Frame suivante 8 | Accepter le filtre 9 | Abandonner le filtre 10 | Ouvrir le panneau Couches 11 | Enlever le filtre 12 | Raccourcir 13 | Annuler 14 | Ajouter 15 | Media à exporter 16 | Mode HDR 17 | Type d\'audio 18 | Exporté 19 | En cours d\'exportation… 20 | Ignorer 21 | Erreur 22 | Sélectionner un fichier à modifier 23 | Projet 24 | Couches vidéo 25 | Avance 10 secondes 26 | Lecture/Pause 27 | Relire 5 secondes 28 | Plus d\'options verticales 29 | Framerate 30 | Vitesse 31 | Thème 32 | Utiliser l\'ancien sélecteur de fichier 33 | Couleur du premier plan 34 | Sélecteur de couleur 35 | Accepter 36 | Enregistrer le projet 37 | Utiliser l\'effet de cascade de l\'interface 38 | Frames 39 | Nouvelle frame 40 | Valeur obligatoirement inférieure ou égale à 41 | Vidéo 42 | Nuances de gris 43 | Résolution 44 | Inverser les couleurs 45 | Largeur 46 | Hauteur 47 | Disposition 48 | Échelle 49 | Pivoter 50 | Degrés 51 | Découper 52 | Le framerate doit être inférieur à celui de la vidéo d\'origine 53 | Coupe sans perte (lossless) 54 | Info des paramètres 55 | Activer la coupe sans perte (lossless) ne fera qu\'exporter les sections raccourcies. 56 | Erreur FFmpeg 57 | Ouvrir le panneau Filtres 58 | Icône de couche 59 | Filtres vidéo 60 | Type de vidéo 61 | Couleur de l\'arrière-plan 62 | Texte 63 | Appliquer 64 | Taille 65 | Lorem Ipsum 66 | Police d\'écriture 67 | -------------------------------------------------------------------------------- /app/src/main/res/values-ru/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Настройки 4 | Текст 5 | Скорость 6 | Проект 7 | Экспортировать 8 | Отмена 9 | Ошибка 10 | Тема 11 | Видео 12 | Добавить 13 | Разрешение 14 | Размер 15 | Назад 16 | Выберите файл для редактирования 17 | Цвет фона 18 | Шрифт 19 | Принять 20 | Выбор цвета 21 | Цвет текста 22 | Сохранить проект 23 | Применить 24 | Новый кадр 25 | Кадры 26 | Каскадный интерфейс 27 | Количество введённых кадров должно быть меньше или равно 28 | Экспортировано 29 | Применить фильтр 30 | Отменить фильтр 31 | Удалить фильтр 32 | Экспорт медиа 33 | HDR-режим 34 | Тип аудио 35 | Тип видео 36 | Закрыть 37 | Слои 38 | Перемотать на 10 секунд вперёд 39 | Воспроизведение/Пауза 40 | Перемотать на 5 секунд назад 41 | Частота кадров 42 | Старый метод выбора файла 43 | Нарезка 44 | Значок 45 | Предыдущий кадр 46 | Следующий кадр 47 | Панель фильтров 48 | Панель слоёв 49 | Фильтры 50 | Больше вертикальных опций 51 | Идёт экспорт 52 | Ориентация 53 | Монохром 54 | Негатив 55 | Ширина 56 | Высота 57 | Угол поворота 58 | Частота кадров должна быть ниже, чем у исходного видео 59 | Обрезать 60 | Компоновка 61 | Масштаб 62 | По умолчанию 63 | Lossless cut 64 | Включение функции «Lossless cut» позволит экспортировать только обрезки. 65 | Ошибка FFmpeg 66 | Информация о настройке 67 | Чёрная тема 68 | Отказано в доступе, предоставьте разрешение на использование видео 69 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Open Video Editor 3 | Back 4 | Export 5 | Settings 6 | Decrement frame 7 | Increment frame 8 | Accept filter 9 | Decline filter 10 | Open layer drawer 11 | Open filter drawer 12 | Layer icon 13 | Remove filter 14 | Video Filters 15 | Trim 16 | Cancel 17 | Add 18 | Media to Export 19 | HDR Mode 20 | Audio Type 21 | Video Type 22 | Exported 23 | Exporting 24 | Dismiss 25 | Error 26 | Select a file to edit 27 | Project 28 | Video Layers 29 | Forward 10 seconds 30 | Play/Pause 31 | Replay 5 seconds 32 | More vertical options 33 | Framerate 34 | Speed 35 | Theme 36 | Use legacy file picker 37 | Background Color 38 | Foreground Color 39 | Text 40 | Set 41 | Size 42 | Color Picker 43 | Accept 44 | Save project 45 | Use UI cascading effect 46 | Frames 47 | New frame 48 | Input must be less than or equal to 49 | Video 50 | Grayscale 51 | Invert Colors 52 | Resolution 53 | Width 54 | Height 55 | Layout 56 | Scale 57 | X 58 | Y 59 | Rotate 60 | Degrees 61 | Crop 62 | Framerate must be lower than the framerate of the original video 63 | Lossless cut 64 | Setting info 65 | Enabling lossless cut will only export trims. 66 | FFmpeg Error 67 | Default 68 | Font 69 | AMOLED dark theme 70 | Permission denied, grant video permissions 71 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/videoeditor/VideoEditorActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor.videoeditor 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import android.os.Build 6 | import android.os.Bundle 7 | import android.widget.Toast 8 | import androidx.activity.ComponentActivity 9 | import androidx.activity.compose.setContent 10 | import androidx.activity.result.ActivityResultLauncher 11 | import androidx.activity.result.contract.ActivityResultContracts 12 | import androidx.compose.runtime.collectAsState 13 | import androidx.compose.runtime.getValue 14 | import androidx.lifecycle.viewmodel.compose.viewModel 15 | import io.github.devhyper.openvideoeditor.R 16 | import io.github.devhyper.openvideoeditor.misc.PROJECT_MIME_TYPE 17 | import io.github.devhyper.openvideoeditor.misc.setImmersiveMode 18 | import io.github.devhyper.openvideoeditor.misc.setupSystemUi 19 | 20 | 21 | class VideoEditorActivity : ComponentActivity() { 22 | private lateinit var createDocument: ActivityResultLauncher 23 | private lateinit var createProject: ActivityResultLauncher 24 | private lateinit var requestVideoPermission: ActivityResultLauncher 25 | private lateinit var viewModel: VideoEditorViewModel 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | 29 | setupSystemUi() 30 | 31 | viewModel = VideoEditorViewModel() 32 | 33 | window.decorView.setOnSystemUiVisibilityChangeListener { 34 | viewModel.setControlsVisible(it == 0) 35 | } 36 | 37 | createDocument = registerForActivityResult( 38 | ActivityResultContracts.CreateDocument( 39 | "video/mp4" 40 | ) 41 | ) { uri -> 42 | if (uri != null) { 43 | viewModel.setOutputPath(uri.toString()) 44 | } 45 | } 46 | createProject = registerForActivityResult( 47 | ActivityResultContracts.CreateDocument( 48 | PROJECT_MIME_TYPE 49 | ) 50 | ) { uri -> 51 | if (uri != null) { 52 | viewModel.setProjectOutputPath(uri.toString()) 53 | } 54 | } 55 | requestVideoPermission = 56 | registerForActivityResult(ActivityResultContracts.RequestPermission()) { 57 | if (it) { 58 | recreate() 59 | } else { 60 | val text = getString(R.string.permission_denied_grant_video_permissions) 61 | val duration = Toast.LENGTH_SHORT 62 | 63 | val toast = Toast.makeText(this, text, duration) 64 | toast.show() 65 | } 66 | } 67 | 68 | var uri: String? = null 69 | if (intent.action == Intent.ACTION_EDIT || intent.action == Intent.ACTION_VIEW) { 70 | intent.dataString?.let { 71 | uri = it 72 | } 73 | } else if (intent.action == Intent.ACTION_SEND) { 74 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 75 | (intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))?.let { 76 | uri = it.toString() 77 | } 78 | } else { 79 | @Suppress("DEPRECATION") 80 | (intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri)?.let { 81 | uri = it.toString() 82 | } 83 | } 84 | } 85 | 86 | uri?.let { 87 | setContent { 88 | viewModel = viewModel { viewModel } 89 | val controlsVisible by viewModel.controlsVisible.collectAsState() 90 | setImmersiveMode(!controlsVisible) 91 | VideoEditorScreen(it, createDocument, createProject, requestVideoPermission) 92 | } 93 | } ?: finish() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/res/values-el/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Φίλτρα 4 | Εξαγωγή 5 | Πίσω 6 | Ρυθμίσεις 7 | Καρέ μείωσης 8 | Καρέ αύξησης 9 | Αποδοχή φίλτρου 10 | Κόψιμο 11 | Απόρριψη φίλτρου 12 | Άνοιγμα συρταριού επίπεδων 13 | Άνοιγμα συρταριού φίλτρων 14 | Εικονίδιο επιπέδου 15 | Αφαίρεση φίλτρου 16 | Ακύρωση 17 | Προσθήκη 18 | Μέσα για εξαγωγή 19 | Κατάσταση HDR 20 | Τύπος ήχου 21 | Τύπος βίντεο 22 | Γίνεται εξαγωγή 23 | Επίπεδα βίντεο 24 | Ταχύτητα 25 | Εξήχθη 26 | ΟΚ 27 | Θέμα 28 | Σφάλμα 29 | Επιλέξτε αρχείο 30 | Έργο 31 | Αναπαραγωγή/Παύση 32 | Επανάληψη 5 δευτ/των 33 | Ρυθμός καρέ 34 | Μπροστά 10 δευτ/τα 35 | Περισσότερες κάθετες επιλογές 36 | Χρώμα φόντου 37 | Κείμενο 38 | Ορισμός 39 | Χρήση παλαιού επιλογέα αρχείων 40 | Χρώμα προσκήνιου 41 | Πλάτος 42 | Μέγεθος 43 | Αποθήκευση έργου 44 | Επιλογή χρώματος 45 | Αποδοχή 46 | Ύψος 47 | Εφέ καταρράκτη διεπαφής χρήστης 48 | Η εισαγωγή πρέπει να είναι λιγότερη ή ίση με 49 | Καρέ 50 | Νέο καρέ 51 | Βίντεο 52 | Ασπρόμαυρο 53 | Αντιστροφή χρωμάτων 54 | Ανάλυση 55 | Περικοπή 56 | Κλιμάκωση 57 | Διάταξη 58 | Περιστροφή 59 | Μοίρες 60 | Ο ρυθμός καρέ πρέπει να είναι χαμηλότερος από τον ρυθμό καρέ του αρχικού βίντεο 61 | Πληροφορίες ρύθμισης 62 | Μη απωλεστική αποκοπή 63 | Σφάλμα FFmpeg 64 | Προεπιλεγμένο 65 | Σκούρο θέμα AMOLED 66 | Η ενεργοποίηση της μη απωλεστικής αποκοπής θα εξάγει μόνο αποκομμένα τμήματα. 67 | Φόντο 68 | Η άδεια απορρίφθηκε, παραχωρήστε άδειες βίντεο 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/res/values-pt-rBR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Voltar 4 | Exportar 5 | Configurações 6 | Frame anterior 7 | Aceitar filtro 8 | Recusar filtro 9 | Frame seguinte 10 | Abrir painel de camadas 11 | Remover filtro 12 | Abrir painel de filtro 13 | Ícone da camada 14 | Cancelar 15 | Adicionar 16 | Exportar mídia 17 | Modo HDR 18 | Exportando 19 | Selecione um arquivo para editar 20 | Camadas de vídeo 21 | Avançar 10 segundos 22 | Reproduzir / pausar 23 | Mais opções verticais 24 | Taxa de quadros 25 | Velocidade 26 | Tema 27 | Usar seletor de arquivos antigo 28 | Aplicar 29 | Tamanho 30 | Aceitar 31 | Usar efeito em cascata da interface do usuário 32 | Frames 33 | Novo frame 34 | Vídeo 35 | Escala de cinza 36 | Inverter cores 37 | Resolução 38 | Graus 39 | Corte livre 40 | A taxa de quadros deve ser menor que a do vídeo original 41 | Corte sem perdas 42 | Informações da configuração 43 | Erro FFmpeg 44 | Fonte 45 | Filtros de vídeo 46 | Cortar 47 | Tipo de áudio 48 | Tipo de vídeo 49 | Exportado 50 | Fechar 51 | Erro 52 | Projeto 53 | Retroceder 5 segundos 54 | Cor de fundo 55 | Texto 56 | Cor do primeiro plano 57 | Seletor de cores 58 | Salvar projeto 59 | Se habilitar o corte sem perdas, só exportará as seções cortadas. 60 | Padrão 61 | O valor deve ser menor ou igual a 62 | Largura 63 | Altura 64 | Layout 65 | Escala 66 | Girar 67 | Permissão negada, conceda as permissões de vídeo 68 | Tema escuro AMOLED 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/res/values-bg/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Изнеси 4 | Настройки 5 | Приеми филтър 6 | Откажи филтър 7 | Отвори чекмеджето за слой 8 | Премахни филтър 9 | Подрязване 10 | Отказ 11 | Добави 12 | Медия за изнасяне 13 | HDR режим 14 | Аудио тип 15 | Видео тип 16 | Изнесени 17 | Слой на икона 18 | Отхвърли 19 | Избери файл за редактиране 20 | Проект 21 | Видео слоеве 22 | Изпълни/Пауза 23 | Повторение 5 секунди 24 | Скорост 25 | Тема 26 | Използвай наследен избор на файл 27 | Текст 28 | Задай 29 | Размер 30 | Цвят на заден план 31 | Честота на кадрите 32 | Запази проекта 33 | Кадри 34 | Нов кадър 35 | Входът трябва да е по-малък или равен на 36 | Скала на сивото 37 | Резолюция 38 | Ширина 39 | Височина 40 | Оформление 41 | Мащаб 42 | Завъртане 43 | Степени 44 | Изрязване 45 | Изрязване без загуба 46 | Информация за настройка 47 | FFmpeg грешка 48 | По подразбиране 49 | Шрифт 50 | AMOLED тъмна тема 51 | Разрешението е отказано, предоставете разрешения за видео 52 | Назад 53 | Намаляване на кадър 54 | Увеличаване на кадър 55 | Отвори чекмеджето за филтър 56 | Видео филтри 57 | Изнасяне 58 | Грешка 59 | Напред 10 секунди 60 | Още вертикални опции 61 | Цвят на преден план 62 | Приеми 63 | Избор на цвят 64 | Използвай каскаден ефект на потребителския интерфейс 65 | Видео 66 | Обърнати цветове 67 | Честотата на кадрите трябва да е по-ниска от тази на оригиналното видео 68 | Активирането на изрязване без загуби ще изнесе само изрязани. 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/res/values-de/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Rahmen vergrößern 4 | Filter akzeptieren 5 | Filter ablehnen 6 | Ebenenschublade öffnen 7 | Filterschublade öffnen 8 | Ebenen Symbol 9 | Videoebenen 10 | 10 Sekunden vorwärts 11 | 5 Sekunden wiederholen 12 | Mehr vertikale Optionen 13 | Bildwiederholrate 14 | Legacy-Dateiauswahl verwenden 15 | Hintergrundfarbe 16 | Vordergrundfarbe 17 | Setzen 18 | Farbauswahl 19 | Akzeptieren 20 | UI-Kaskadeneffekt verwenden 21 | Rahmen 22 | Neuer Rahmen 23 | Graustufe 24 | Farben umkehren 25 | Auflösung 26 | Breite 27 | Höhe 28 | Drehen 29 | Grade 30 | Zuschneiden 31 | Bildwiederholrate muss niedriger sein als die Bildwiederholrate des Originalvideos 32 | Verlustfreier Schnitt 33 | Einstellungsinformationen 34 | Durch die Aktivierung des verlustfreien Schnitts werden nur Beschnitte exportiert. 35 | FFmpeg Fehler 36 | Standard 37 | Schrift 38 | AMOLED Dunkles Thema 39 | HDR Modus 40 | Zurück 41 | Exportieren 42 | Einstellungen 43 | Rahmen verkleinern 44 | Filter entfernen 45 | Videofilter 46 | Abbrechen 47 | Trimmen 48 | Hinzufügen 49 | Zu Medien exportieren 50 | Audiotyp 51 | Videotyp 52 | Exportiert 53 | Exportiert gerade 54 | Schließen 55 | Fehler 56 | Wähle eine Datei zum Bearbeiten 57 | Projekt 58 | Wiedergabe/Pause 59 | Geschwindigkeit 60 | Thema 61 | Text 62 | Größe 63 | Projekt speichern 64 | Eingabe muss kleiner als oder gleich sein zu 65 | Layout 66 | Video 67 | Skala 68 | Erlaubnis verweigert, Videoerlaubnis erteilen 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/res/values-uk/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Експорт 4 | Налаштування 5 | Застосувати фільтр 6 | Назад 7 | Відхилити фільтр 8 | Декремент кадру 9 | Відкрити ящик шарів 10 | Відкрити ящик фільтра 11 | Значок шару 12 | Зняти фільтр 13 | Відеофільтри 14 | Обрізка 15 | Скасувати 16 | Медіа для експорту 17 | Режим HDR 18 | Тип звуку 19 | Відхилити 20 | Помилка 21 | Відеошари 22 | Уперед на 10 секунд 23 | Відтворення/Пауза 24 | Більше вертикальних варіантів 25 | Частота кадрів 26 | Тема 27 | Колір фону 28 | встановити 29 | Розмір 30 | Прийняти 31 | Використовуйте каскадний ефект інтерфейсу користувача 32 | Нова рамка 33 | Вхід має бути менше або дорівнювати 34 | Інвертувати кольори 35 | Роздільна здатність 36 | Ширина 37 | Висота 38 | Макет 39 | Обертати 40 | Кадрування 41 | Вирізання без втрат 42 | Інформація про налаштування 43 | Якщо ввімкнути обрізання без втрат, буде експортовано лише обрізки. 44 | За замовчуванням 45 | Шрифт 46 | Темна тема AMOLED 47 | У дозволі відмовлено, надайте дозволи на відео 48 | Збільшити рамку 49 | Демонструвати 50 | додати 51 | Тип відео 52 | Експортовано 53 | Відео 54 | Експортуємо 55 | Виберіть файл для редагування 56 | Повторити 5 секунд 57 | Швидкість 58 | Палітра кольорів 59 | Використовуйте застарілий засіб вибору файлів 60 | Колір переднього плану 61 | Текст 62 | Зберегти проект 63 | Рамки 64 | Ступені 65 | Відтінки сірого 66 | Масштаб 67 | Частота кадрів має бути нижчою за частоту кадрів оригінального відео 68 | Помилка FFmpeg 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/res/values-pt/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exportar 4 | Fotograma anterior 5 | Aceitar filtro 6 | Recusar filtro 7 | Modo HDR 8 | Tipo de áudio 9 | A exportar 10 | Fechar 11 | Erro 12 | Selecione um ficheiro para editar 13 | Projeto 14 | Camadas de vídeo 15 | Avançar 10 segundos 16 | Retroceder 5 segundos 17 | Fotogramas por segundo 18 | Voltar 19 | Configurações 20 | Remover filtro 21 | Filtros de vídeo 22 | Cortar 23 | Cancelar 24 | Adicionar 25 | Exportar media 26 | Tipo de vídeo 27 | Exportado 28 | Reproduzir / pausar 29 | Mais opções verticais 30 | Velocidade 31 | Tema 32 | Abrir painel de camadas 33 | Fotograma seguinte 34 | Abrir painel de filtros 35 | Ícone da camada 36 | Utilizar selecionador de ficheiros antigo 37 | Cor de fundo 38 | Cor de primeiro plano 39 | Texto 40 | Aplicar 41 | Tamanho 42 | Seletor de cores 43 | Aceitar 44 | Guardar projeto 45 | Usar efeito em cascata da interface 46 | Fotogramas 47 | Novo fotograma 48 | A entrada deve ser menor ou igual a 49 | Vídeo 50 | Escala de cinza 51 | Inverter cores 52 | Resolução 53 | Largura 54 | Altura 55 | Escala 56 | Disposição 57 | A velocidade de fotogramas deve ser inferior à velocidade de fotogramas do vídeo original 58 | Rodar 59 | Graus 60 | Corte livre 61 | Corte sem perdas 62 | Informações da configuração 63 | Se ativar o corte sem perdas, só exportará as secções cortadas. 64 | Erro FFmpeg 65 | Predefinido 66 | Tipo de letra 67 | Tema escuro AMOLED 68 | Permissão negada, conceda as permissões de vídeo 69 | -------------------------------------------------------------------------------- /app/src/main/res/values-es/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Atras 4 | Exportar 5 | Ajustes 6 | Disminuir marco 7 | Incrementar el marco 8 | Aplicar filtro 9 | Rechazar el filtro 10 | Abrir el panel de filtros 11 | icono de capa 12 | Eliminar el filtro 13 | Filtros de vídeo 14 | Recortar 15 | Cancelar 16 | Añadir 17 | Modo HDR 18 | Tipo de audio 19 | Tipo de vídeo 20 | Exportado 21 | Exportando 22 | Descartar 23 | Error 24 | Seleccionar un archivo para editar 25 | Proyecto 26 | Capas de vídeo 27 | Reproducir/Pausar 28 | Repetir durante 5 segundos 29 | Guardar el proyecto 30 | Vídeo 31 | Escala de grises 32 | Avanzar 10 segundos 33 | Más opciones verticales 34 | Fotogramas por segundo 35 | Velocidad 36 | Tema 37 | Utilice el selector de archivos heredado 38 | Color de fondo 39 | Color frontal 40 | Texto 41 | Establecer 42 | Tamaño 43 | Selector de color 44 | Aceptar 45 | Utilizar el efecto cascada de la interfaz de usuario 46 | Fotogramas 47 | Nuevo fotograma 48 | La entrada debe ser inferior o igual a 49 | Colores invertidos 50 | Resolución 51 | Ancho 52 | Disposición 53 | Escala 54 | Girar 55 | Por defecto 56 | Fuente 57 | Recortar sin pérdidas 58 | Información de la configuración 59 | Error FFmpeg 60 | Abrir el panel de capas 61 | Exportar medios 62 | Alto 63 | Grados 64 | La velocidad de fotogramas debe ser inferior a la velocidad de fotogramas del vídeo original 65 | Al habilitar el corte sin pérdidas solo se exportarán las secciones acortadas. 66 | Cortar 67 | Tema oscuro AMOLED 68 | Permiso denegado, conceder permisos de vídeo 69 | -------------------------------------------------------------------------------- /app/src/main/res/values-ta/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | பிரேம்ரேட் 4 | வேகம் 5 | இயல்புநிலை 6 | எழுத்துரு 7 | ஏற்றுமதி 8 | அமைப்புகள் 9 | குறைவு சட்டகம் 10 | அதிகரிப்பு சட்டகம் 11 | வடிகட்டியை ஏற்றுக்கொள்ளுங்கள் 12 | வீழ்ச்சி வடிகட்டி 13 | திறந்த அடுக்கு அலமாரியை 14 | வடிகட்டி அலமாரியை திறந்த 15 | அடுக்கு படவுரு 16 | வடிகட்டியை அகற்று 17 | வீடியோ வடிப்பான்கள் 18 | ஒழுங்கமைக்கவும் 19 | ரத்துசெய் 20 | கூட்டு 21 | ஏற்றுமதி செய்ய ஊடகங்கள் 22 | எச்டிஆர் பயன்முறை 23 | ஆடியோ வகை 24 | வீடியோ வகை 25 | ஏற்றுமதிசெய்யப்பட்டது 26 | ஏற்றுமதி செய்கிறது 27 | தள்ளுபடி 28 | பிழை 29 | திருத்த ஒரு கோப்பைத் தேர்ந்தெடுக்கவும் 30 | திட்டம் 31 | வீடியோ அடுக்குகள் 32 | முன்னோக்கி 10 வினாடிகள் 33 | விளையாடு/இடைநிறுத்தம் 34 | 5 வினாடிகள் மீண்டும் இயக்கவும் 35 | மேலும் செங்குத்து விருப்பங்கள் 36 | கருப்பொருள் 37 | மரபு கோப்பு தேர்வாளரைப் பயன்படுத்தவும் 38 | பின்னணி நிறம் 39 | முன்புற நிறம் 40 | உரை 41 | கணம் 42 | அளவு 43 | வண்ண தேர்வாளர் 44 | ஏற்றுக்கொள் 45 | திட்டத்தை சேமிக்கவும் 46 | இடைமுகம் அடுக்கு விளைவைப் பயன்படுத்தவும் 47 | சட்டங்கள் 48 | புதிய சட்டகம் 49 | ஒளிதோற்றம் 50 | உள்ளீடு குறைவாகவோ அல்லது சமமாகவோ இருக்க வேண்டும் 51 | கிரேச்கேல் 52 | வண்ணங்களை தலைகீழ் 53 | பகுத்தல் 54 | அகலம் 55 | உயரம் 56 | மனையமைவு 57 | அளவீடு 58 | சுழற்றுங்கள் 59 | டிகிரி 60 | பயிர் 61 | அசல் வீடியோவின் பிரேம்ரேட்டை விட ஃப்ரேம்ரேட் குறைவாக இருக்க வேண்டும் 62 | இழப்பற்ற வெட்டு 63 | செய்தி அமைத்தல் 64 | இழப்பற்ற வெட்டுக்கு உதவுவது டிரிம்களை மட்டுமே ஏற்றுமதி செய்யும். 65 | அமோல்ட் இருண்ட கருப்பொருள் 66 | இசைவு மறுக்கப்பட்டது, வீடியோ அனுமதிகளை வழங்கவும் 67 | Ffmpeg பிழை 68 | பின் 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/res/values-tr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Video Düzenleyiciyi Aç 4 | Geri 5 | Dışa Aktar 6 | Ayarlar 7 | Azaltma çerçevesi 8 | Artırma çerçevesi 9 | Filtreyi kabul et 10 | Filtreyi reddet 11 | Katman çekmecesini aç 12 | Filtre çekmecesini aç 13 | Katman simgesi 14 | Filtreyi kaldır 15 | Video Filtreleri 16 | Düzelt 17 | İptal 18 | Ekle 19 | Dışa Aktarılacak Medya 20 | HDR Modu 21 | Ses Türü 22 | Video Türü 23 | Dışa aktarıldı 24 | Dışa aktarılıyor 25 | Kapat 26 | Hata 27 | Düzenlemek için bir dosya seçin 28 | Proje 29 | Video Katmanları 30 | 10 saniye ileri sar 31 | Oynat/Duraklat 32 | 5 saniye tekrar oynat 33 | Daha fazla dikey seçenek 34 | Kare hızı 35 | Hız 36 | Tema 37 | Eski dosya seçiciyi kullan 38 | Arka Plan Rengi 39 | Ön Plan Rengi 40 | Metin 41 | Set 42 | Boyut 43 | Renk Seçici 44 | Kabul Et 45 | Projeyi kaydet 46 | Kullanıcı arabirimi basamaklama efekti kullan 47 | Çerçeveler 48 | Yeni çerçeve 49 | Girdi şundan küçük veya şuna eşit olmalıdır 50 | Video 51 | Gri tonlamalı 52 | Renkleri Tersine Çevir 53 | Çözünürlük 54 | Genişlik 55 | Yükseklik 56 | Düzen 57 | Ölçek 58 | X 59 | Y 60 | Döndür 61 | Derece 62 | Kırp 63 | Kare hızı, orijinal videonun kare hızından daha düşük olmalıdır 64 | Kayıpsız kesim 65 | Ayar bilgisi 66 | Kayıpsız kesimi etkinleştirmek yalnızca kırpmaları dışa aktarır. 67 | FFmpeg Hatası 68 | Varsayılan 69 | Yazı tipi 70 | AMOLED koyu tema 71 | İzin reddedildi, video izinlerini verin 72 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/videoeditor/UserEffects.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor.videoeditor 2 | 3 | import androidx.compose.material.icons.Icons 4 | import androidx.compose.material.icons.automirrored.filled.RotateRight 5 | import androidx.compose.material.icons.filled.Crop 6 | import androidx.compose.material.icons.filled.Filter 7 | import androidx.compose.material.icons.filled.FormatSize 8 | import androidx.compose.material.icons.filled.InvertColors 9 | import androidx.compose.material.icons.filled.TextFormat 10 | import androidx.compose.material.icons.filled.Tv 11 | import androidx.media3.effect.Presentation 12 | import androidx.media3.effect.RgbFilter 13 | import androidx.media3.effect.ScaleAndRotateTransformation 14 | import io.github.devhyper.openvideoeditor.R 15 | import io.github.devhyper.openvideoeditor.misc.validateFloat 16 | import io.github.devhyper.openvideoeditor.misc.validateFloatAndNonzero 17 | import io.github.devhyper.openvideoeditor.misc.validateUIntAndNonzero 18 | import kotlinx.collections.immutable.ImmutableList 19 | import kotlinx.collections.immutable.persistentListOf 20 | 21 | val userEffectsArray: ImmutableList = persistentListOf( 22 | UserEffect( 23 | R.string.grayscale, 24 | { Icons.Filled.Filter }) { RgbFilter.createGrayscaleFilter() }, 25 | UserEffect( 26 | R.string.invert_colors, 27 | { Icons.Filled.InvertColors }) { RgbFilter.createInvertedFilter() } 28 | ) 29 | 30 | val dialogUserEffectsArray: ImmutableList = persistentListOf( 31 | DialogUserEffect( 32 | R.string.resolution, 33 | { Icons.Filled.Tv }, 34 | persistentListOf( 35 | EffectDialogSetting(key = "Width", R.string.width, textfieldValidation = { 36 | validateUIntAndNonzero(it) 37 | } 38 | ), 39 | EffectDialogSetting(key = "Height", R.string.height, textfieldValidation = 40 | { 41 | validateUIntAndNonzero(it) 42 | } 43 | ), 44 | EffectDialogSetting( 45 | key = "Layout", R.string.layout, dropdownOptions = 46 | mutableListOf( 47 | "Scale to fit", 48 | "Scale to fit with crop", 49 | "Stretch to fit", 50 | ) 51 | ) 52 | ) 53 | ) { args -> 54 | val width = args["Width"]!!.toInt() 55 | val height = args["Height"]!!.toInt() 56 | val layout: Int = when (args["Layout"]) { 57 | "Scale to fit" -> Presentation.LAYOUT_SCALE_TO_FIT 58 | "Scale to fit with crop" -> Presentation.LAYOUT_SCALE_TO_FIT_WITH_CROP 59 | "Stretch to fit" -> Presentation.LAYOUT_STRETCH_TO_FIT 60 | else -> Presentation.LAYOUT_SCALE_TO_FIT 61 | } 62 | { Presentation.createForWidthAndHeight(width, height, layout) } 63 | }, 64 | DialogUserEffect( 65 | R.string.scale, 66 | { Icons.Filled.FormatSize }, 67 | persistentListOf( 68 | EffectDialogSetting(key = "X", R.string.x, textfieldValidation = { 69 | validateFloatAndNonzero(it) 70 | } 71 | ), 72 | EffectDialogSetting(key = "Y", R.string.y, textfieldValidation = 73 | { 74 | validateFloatAndNonzero(it) 75 | } 76 | ) 77 | ) 78 | ) { args -> 79 | val x = args["X"]!!.toFloat() 80 | val y = args["Y"]!!.toFloat(); 81 | { ScaleAndRotateTransformation.Builder().setScale(x, y).build() } 82 | }, 83 | DialogUserEffect( 84 | R.string.rotate, 85 | { Icons.AutoMirrored.Filled.RotateRight }, 86 | persistentListOf( 87 | EffectDialogSetting( 88 | key = "Degrees", 89 | stringResId = R.string.degrees, 90 | textfieldValidation = { 91 | validateFloat(it) 92 | } 93 | ) 94 | ) 95 | ) { args -> 96 | val degrees = args["Degrees"]!!.toFloat(); 97 | { ScaleAndRotateTransformation.Builder().setRotationDegrees(degrees).build() } 98 | } 99 | ) 100 | 101 | val onVideoUserEffectsArray: ImmutableList = persistentListOf( 102 | OnVideoUserEffect( 103 | R.string.text, 104 | { Icons.Filled.TextFormat } 105 | ) { TextEditor(it) }, 106 | OnVideoUserEffect( 107 | R.string.crop, 108 | { Icons.Filled.Crop } 109 | ) { CropEditor(it) } 110 | ) 111 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/main/MainScreen.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor.main 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import androidx.activity.result.ActivityResultLauncher 6 | import androidx.activity.result.PickVisualMediaRequest 7 | import androidx.activity.result.contract.ActivityResultContracts 8 | import androidx.compose.foundation.layout.Arrangement 9 | import androidx.compose.foundation.layout.Box 10 | import androidx.compose.foundation.layout.Column 11 | import androidx.compose.foundation.layout.fillMaxSize 12 | import androidx.compose.foundation.layout.padding 13 | import androidx.compose.material.icons.Icons 14 | import androidx.compose.material.icons.filled.Settings 15 | import androidx.compose.material3.Button 16 | import androidx.compose.material3.ExperimentalMaterial3Api 17 | import androidx.compose.material3.FilledTonalButton 18 | import androidx.compose.material3.Icon 19 | import androidx.compose.material3.IconButton 20 | import androidx.compose.material3.MaterialTheme 21 | import androidx.compose.material3.Scaffold 22 | import androidx.compose.material3.Surface 23 | import androidx.compose.material3.Text 24 | import androidx.compose.material3.TopAppBar 25 | import androidx.compose.runtime.Composable 26 | import androidx.compose.ui.Alignment 27 | import androidx.compose.ui.Modifier 28 | import androidx.compose.ui.platform.LocalContext 29 | import androidx.compose.ui.res.stringResource 30 | import androidx.compose.ui.text.style.TextAlign 31 | import androidx.compose.ui.unit.dp 32 | import io.github.devhyper.openvideoeditor.R 33 | import io.github.devhyper.openvideoeditor.misc.PROJECT_MIME_TYPE 34 | import io.github.devhyper.openvideoeditor.settings.SettingsActivity 35 | import io.github.devhyper.openvideoeditor.ui.theme.OpenVideoEditorTheme 36 | 37 | @OptIn(ExperimentalMaterial3Api::class) 38 | @Composable 39 | fun MainScreen( 40 | pickMedia: ActivityResultLauncher, 41 | pickProject: ActivityResultLauncher> 42 | ) { 43 | val activity = LocalContext.current as Activity 44 | OpenVideoEditorTheme { 45 | Surface( 46 | modifier = Modifier 47 | .fillMaxSize(), 48 | color = MaterialTheme.colorScheme.background 49 | ) { 50 | Scaffold( 51 | topBar = { 52 | TopAppBar( 53 | title = { 54 | Text( 55 | stringResource(R.string.app_name), 56 | ) 57 | }, 58 | actions = { 59 | IconButton(onClick = { 60 | val intent = Intent(activity, SettingsActivity::class.java) 61 | activity.startActivity(intent) 62 | }) { 63 | Icon( 64 | imageVector = Icons.Filled.Settings, 65 | contentDescription = stringResource(R.string.settings) 66 | ) 67 | } 68 | } 69 | ) 70 | }, content = { innerPadding -> 71 | Box( 72 | modifier = Modifier 73 | .padding(innerPadding) 74 | .fillMaxSize() 75 | ) { 76 | Column( 77 | modifier = Modifier.align(Alignment.Center), 78 | verticalArrangement = Arrangement.spacedBy(32.dp), 79 | horizontalAlignment = Alignment.CenterHorizontally 80 | ) 81 | { 82 | Text( 83 | stringResource(R.string.select_a_file_to_edit), 84 | style = MaterialTheme.typography.headlineLarge, 85 | textAlign = TextAlign.Center 86 | ) 87 | Button(onClick = { 88 | pickMedia.launch( 89 | PickVisualMediaRequest( 90 | ActivityResultContracts.PickVisualMedia.VideoOnly 91 | ) 92 | ) 93 | }, modifier = Modifier) { 94 | Text( 95 | style = MaterialTheme.typography.titleLarge, 96 | text = stringResource(R.string.video) 97 | ) 98 | } 99 | FilledTonalButton(onClick = { 100 | pickProject.launch( 101 | arrayOf( 102 | PROJECT_MIME_TYPE 103 | ) 104 | ) 105 | }, modifier = Modifier) { 106 | Text( 107 | style = MaterialTheme.typography.titleLarge, 108 | text = stringResource(R.string.project) 109 | ) 110 | } 111 | } 112 | } 113 | } 114 | ) 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/misc/Utils.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor.misc 2 | 3 | import android.content.Context 4 | import android.content.res.Resources 5 | import android.graphics.Paint 6 | import android.graphics.Typeface 7 | import android.media.MediaMetadataRetriever 8 | import android.net.Uri 9 | import android.provider.OpenableColumns 10 | import android.text.Spannable 11 | import android.text.SpannableString 12 | import android.text.TextPaint 13 | import android.text.style.TypefaceSpan 14 | import android.util.TypedValue 15 | import androidx.activity.ComponentActivity 16 | import androidx.activity.enableEdgeToEdge 17 | import androidx.compose.foundation.gestures.awaitEachGesture 18 | import androidx.compose.foundation.gestures.awaitFirstDown 19 | import androidx.compose.foundation.gestures.waitForUpOrCancellation 20 | import androidx.compose.foundation.interaction.InteractionSource 21 | import androidx.compose.runtime.Composable 22 | import androidx.compose.runtime.getValue 23 | import androidx.compose.runtime.rememberUpdatedState 24 | import androidx.compose.ui.Modifier 25 | import androidx.compose.ui.composed 26 | import androidx.compose.ui.input.pointer.pointerInput 27 | import androidx.compose.ui.platform.LocalDensity 28 | import androidx.compose.ui.unit.Dp 29 | import androidx.core.view.WindowCompat 30 | import androidx.core.view.WindowInsetsCompat 31 | import androidx.core.view.WindowInsetsControllerCompat 32 | import androidx.media3.effect.OverlayEffect 33 | import androidx.media3.effect.TextureOverlay 34 | import com.google.common.collect.ImmutableList 35 | import kotlinx.coroutines.coroutineScope 36 | import kotlinx.coroutines.delay 37 | import kotlinx.coroutines.launch 38 | import java.util.concurrent.TimeUnit 39 | 40 | 41 | fun getFileNameFromUri(context: Context, uri: Uri): String { 42 | var fileName: String? = null 43 | val cursor = context.contentResolver.query(uri, null, null, null, null) 44 | val nameIndex = cursor?.getColumnIndex(OpenableColumns.DISPLAY_NAME) 45 | val cursorHasValue = cursor?.moveToFirst() 46 | if (cursorHasValue == true) { 47 | fileName = nameIndex?.let { cursor.getString(it) } 48 | } 49 | cursor?.close() 50 | if (!fileName.isNullOrEmpty()) { 51 | return fileName 52 | } 53 | return "null" 54 | } 55 | 56 | fun getVideoFileDuration(context: Context, uri: Uri): Long? { 57 | val retriever = MediaMetadataRetriever() 58 | retriever.setDataSource(context, uri) 59 | val time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) 60 | retriever.release() 61 | return time?.toLong() 62 | } 63 | 64 | fun ComponentActivity.setupSystemUi() { 65 | enableEdgeToEdge() 66 | actionBar?.hide() 67 | } 68 | 69 | fun ComponentActivity.setImmersiveMode(enabled: Boolean) { 70 | val windowInsetsController = 71 | WindowCompat.getInsetsController(window, window.decorView) 72 | windowInsetsController.systemBarsBehavior = 73 | WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE 74 | if (enabled) { 75 | windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) 76 | } else { 77 | windowInsetsController.show(WindowInsetsCompat.Type.systemBars()) 78 | } 79 | } 80 | 81 | fun Long.formatMinSec(): String { 82 | return if (this == 0L) { 83 | "00:00" 84 | } else { 85 | String.format( 86 | "%02d:%02d", 87 | TimeUnit.MILLISECONDS.toMinutes(this), 88 | TimeUnit.MILLISECONDS.toSeconds(this) - 89 | TimeUnit.MINUTES.toSeconds( 90 | TimeUnit.MILLISECONDS.toMinutes(this) 91 | ) 92 | ) 93 | } 94 | } 95 | 96 | fun MutableList.move(item: T, newIndex: Int) { 97 | val currentIndex = indexOf(item) 98 | if (currentIndex < 0) return 99 | removeAt(currentIndex) 100 | add(newIndex, item) 101 | } 102 | 103 | fun Modifier.repeatingClickable( 104 | interactionSource: InteractionSource, 105 | enabled: Boolean, 106 | maxDelayMillis: Long = 1000, 107 | minDelayMillis: Long = 5, 108 | delayDecayFactor: Float = .40f, 109 | onClick: () -> Unit 110 | ): Modifier = this.composed { 111 | 112 | val currentClickListener by rememberUpdatedState(onClick) 113 | 114 | pointerInput(interactionSource, enabled) { 115 | coroutineScope { 116 | awaitEachGesture { 117 | val down = awaitFirstDown(requireUnconsumed = false) 118 | val heldButtonJob = launch { 119 | var currentDelayMillis = maxDelayMillis 120 | while (enabled && down.pressed) { 121 | currentClickListener() 122 | delay(currentDelayMillis) 123 | val nextMillis = 124 | currentDelayMillis - (currentDelayMillis * delayDecayFactor) 125 | currentDelayMillis = nextMillis.toLong().coerceAtLeast(minDelayMillis) 126 | } 127 | } 128 | waitForUpOrCancellation() 129 | heldButtonJob.cancel() 130 | } 131 | } 132 | } 133 | } 134 | 135 | fun TextureOverlay.toOverlayEffect(): OverlayEffect { 136 | val overlaysBuilder = ImmutableList.Builder() 137 | overlaysBuilder.add(this) 138 | return OverlayEffect(overlaysBuilder.build()) 139 | } 140 | 141 | fun SpannableString.setFullLengthSpan(what: Any) { 142 | setSpan(what, 0, length, Spannable.SPAN_INCLUSIVE_INCLUSIVE) 143 | } 144 | 145 | fun screenToNdc(screen: Float, screenSize: Float): Float { 146 | return (((screen / screenSize) * 2f) - 1f).coerceIn(-1f..1f) 147 | } 148 | 149 | fun spToPx(sp: Float): Int { 150 | return TypedValue.applyDimension( 151 | TypedValue.COMPLEX_UNIT_SP, 152 | sp, 153 | Resources.getSystem().displayMetrics 154 | ).toInt() 155 | } 156 | 157 | fun LongRange.toLongPair(): Pair { 158 | return Pair(this.first, this.last) 159 | } 160 | 161 | fun Pair.toLongRange(): LongRange { 162 | return LongRange(this.first, this.second) 163 | } 164 | 165 | @Composable 166 | fun Dp.dpToPx() = with(LocalDensity.current) { this@dpToPx.toPx() } 167 | 168 | @Composable 169 | fun Int.pxToDp() = with(LocalDensity.current) { this@pxToDp.toDp() } 170 | 171 | class CompatTypefaceSpan(private val typeface: Typeface) : 172 | TypefaceSpan(null) { 173 | override fun updateDrawState(ds: TextPaint) { 174 | applyCustomTypeFace(ds, typeface) 175 | } 176 | 177 | override fun updateMeasureState(paint: TextPaint) { 178 | applyCustomTypeFace(paint, typeface) 179 | } 180 | 181 | companion object { 182 | private fun applyCustomTypeFace(paint: Paint, tf: Typeface) { 183 | val oldStyle: Int 184 | val old = paint.typeface 185 | oldStyle = old?.style ?: 0 186 | val fake = oldStyle and tf.style.inv() 187 | if (fake and Typeface.BOLD != 0) { 188 | paint.isFakeBoldText = true 189 | } 190 | if (fake and Typeface.ITALIC != 0) { 191 | paint.textSkewX = -0.25f 192 | } 193 | paint.typeface = tf 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/settings/SettingsScreen.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor.settings 2 | 3 | import android.app.Activity 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.lazy.LazyColumn 10 | import androidx.compose.material.icons.Icons 11 | import androidx.compose.material.icons.automirrored.filled.ArrowBack 12 | import androidx.compose.material3.ExperimentalMaterial3Api 13 | import androidx.compose.material3.Icon 14 | import androidx.compose.material3.IconButton 15 | import androidx.compose.material3.MaterialTheme 16 | import androidx.compose.material3.Scaffold 17 | import androidx.compose.material3.Surface 18 | import androidx.compose.material3.Text 19 | import androidx.compose.material3.TopAppBar 20 | import androidx.compose.runtime.Composable 21 | import androidx.compose.runtime.remember 22 | import androidx.compose.runtime.rememberCoroutineScope 23 | import androidx.compose.ui.Alignment 24 | import androidx.compose.ui.Modifier 25 | import androidx.compose.ui.platform.LocalContext 26 | import androidx.compose.ui.res.stringResource 27 | import androidx.compose.ui.text.style.TextAlign 28 | import androidx.compose.ui.unit.dp 29 | import io.github.devhyper.openvideoeditor.R 30 | import io.github.devhyper.openvideoeditor.misc.DropdownSetting 31 | import io.github.devhyper.openvideoeditor.misc.SwitchSetting 32 | import io.github.devhyper.openvideoeditor.misc.move 33 | import io.github.devhyper.openvideoeditor.ui.theme.OpenVideoEditorTheme 34 | import kotlinx.collections.immutable.toImmutableList 35 | import kotlinx.coroutines.launch 36 | 37 | @OptIn(ExperimentalMaterial3Api::class) 38 | @Composable 39 | fun SettingsScreen() { 40 | val context = LocalContext.current 41 | val activity = context as Activity 42 | val scope = rememberCoroutineScope() 43 | val dataStore = remember { SettingsDataStore(context) } 44 | OpenVideoEditorTheme { 45 | Surface( 46 | modifier = Modifier 47 | .fillMaxSize(), 48 | color = MaterialTheme.colorScheme.background 49 | ) { 50 | Scaffold( 51 | topBar = { 52 | TopAppBar( 53 | title = { 54 | Text( 55 | text = stringResource(R.string.settings), 56 | ) 57 | }, 58 | navigationIcon = { 59 | IconButton(onClick = { activity.finish() }) { 60 | Icon( 61 | imageVector = Icons.AutoMirrored.Filled.ArrowBack, 62 | contentDescription = stringResource(R.string.back) 63 | ) 64 | } 65 | } 66 | ) 67 | }, content = { innerPadding -> 68 | LazyColumn( 69 | modifier = Modifier 70 | .padding(innerPadding) 71 | .padding(horizontal = 32.dp) 72 | .fillMaxSize(), 73 | verticalArrangement = Arrangement.spacedBy(16.dp) 74 | ) 75 | { 76 | item { 77 | val theme = dataStore.getThemeBlocking() 78 | val options = mutableListOf("System", "Light", "Dark") 79 | options.move(theme, 0) 80 | SettingRow( 81 | name = stringResource(R.string.theme) 82 | ) { 83 | DropdownSetting( 84 | name = stringResource(R.string.theme), 85 | options = options.toImmutableList(), 86 | onSelectionChanged = { 87 | if (it != theme) { 88 | scope.launch { 89 | dataStore.setTheme(it) 90 | } 91 | } 92 | }) 93 | } 94 | } 95 | item { 96 | val useLegacyFilePicker = dataStore.getLegacyFilePickerBlocking() 97 | SwitchSetting( 98 | name = stringResource(R.string.use_legacy_file_picker), 99 | startChecked = useLegacyFilePicker, 100 | onCheckChanged = { 101 | if (it != useLegacyFilePicker) { 102 | scope.launch { 103 | dataStore.setLegacyFilePicker(it) 104 | } 105 | } 106 | }) 107 | } 108 | item { 109 | val useUiCascadingEffect = dataStore.getUiCascadingEffectBlocking() 110 | SwitchSetting( 111 | name = stringResource(R.string.use_ui_cascading_effect), 112 | startChecked = useUiCascadingEffect, 113 | onCheckChanged = { 114 | if (it != useUiCascadingEffect) { 115 | scope.launch { 116 | dataStore.setUiCascadingEffect(it) 117 | } 118 | } 119 | }) 120 | } 121 | item { 122 | val useAmoled = dataStore.getAmoledBlocking() 123 | SwitchSetting( 124 | name = stringResource(R.string.amoled_dark_theme), 125 | startChecked = useAmoled, 126 | onCheckChanged = { 127 | if (it != useAmoled) { 128 | scope.launch { 129 | dataStore.setAmoled(it) 130 | } 131 | } 132 | }) 133 | } 134 | } 135 | } 136 | ) 137 | } 138 | } 139 | } 140 | 141 | @Composable 142 | fun SettingRow(name: String, value: @Composable () -> Unit) { 143 | Row( 144 | modifier = Modifier.fillMaxWidth(), 145 | horizontalArrangement = Arrangement.SpaceBetween, 146 | verticalAlignment = Alignment.CenterVertically 147 | ) { 148 | Text(modifier = Modifier.padding(end = 16.dp), text = name, textAlign = TextAlign.Center) 149 | value() 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Use the maximum available, or set MAX_FD != -1 to use that value. 89 | MAX_FD=maximum 90 | 91 | warn () { 92 | echo "$*" 93 | } >&2 94 | 95 | die () { 96 | echo 97 | echo "$*" 98 | echo 99 | exit 1 100 | } >&2 101 | 102 | # OS specific support (must be 'true' or 'false'). 103 | cygwin=false 104 | msys=false 105 | darwin=false 106 | nonstop=false 107 | case "$( uname )" in #( 108 | CYGWIN* ) cygwin=true ;; #( 109 | Darwin* ) darwin=true ;; #( 110 | MSYS* | MINGW* ) msys=true ;; #( 111 | NONSTOP* ) nonstop=true ;; 112 | esac 113 | 114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 115 | 116 | 117 | # Determine the Java command to use to start the JVM. 118 | if [ -n "$JAVA_HOME" ] ; then 119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 120 | # IBM's JDK on AIX uses strange locations for the executables 121 | JAVACMD=$JAVA_HOME/jre/sh/java 122 | else 123 | JAVACMD=$JAVA_HOME/bin/java 124 | fi 125 | if [ ! -x "$JAVACMD" ] ; then 126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 127 | 128 | Please set the JAVA_HOME variable in your environment to match the 129 | location of your Java installation." 130 | fi 131 | else 132 | JAVACMD=java 133 | if ! command -v java >/dev/null 2>&1 134 | then 135 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 136 | 137 | Please set the JAVA_HOME variable in your environment to match the 138 | location of your Java installation." 139 | fi 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | 201 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 202 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 203 | 204 | # Collect all arguments for the java command; 205 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 206 | # shell script including quotes and variable substitutions, so put them in 207 | # double quotes to make sure that they get re-expanded; and 208 | # * put everything else in single quotes, so that it's not re-expanded. 209 | 210 | set -- \ 211 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 212 | -classpath "$CLASSPATH" \ 213 | org.gradle.wrapper.GradleWrapperMain \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/videoeditor/OnVideoEditors.kt: -------------------------------------------------------------------------------- 1 | package io.github.devhyper.openvideoeditor.videoeditor 2 | 3 | import android.graphics.Typeface 4 | import android.text.SpannableString 5 | import android.text.style.AbsoluteSizeSpan 6 | import android.text.style.BackgroundColorSpan 7 | import android.text.style.ForegroundColorSpan 8 | import android.text.style.TypefaceSpan 9 | import androidx.compose.foundation.gestures.detectDragGestures 10 | import androidx.compose.foundation.layout.BoxWithConstraints 11 | import androidx.compose.foundation.layout.absoluteOffset 12 | import androidx.compose.foundation.layout.fillMaxSize 13 | import androidx.compose.foundation.text.BasicTextField 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.runtime.getValue 16 | import androidx.compose.runtime.mutableFloatStateOf 17 | import androidx.compose.runtime.mutableIntStateOf 18 | import androidx.compose.runtime.mutableStateOf 19 | import androidx.compose.runtime.remember 20 | import androidx.compose.runtime.setValue 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.graphics.Color 23 | import androidx.compose.ui.graphics.toArgb 24 | import androidx.compose.ui.input.pointer.pointerInput 25 | import androidx.compose.ui.res.stringResource 26 | import androidx.compose.ui.text.TextStyle 27 | import androidx.compose.ui.text.font.Font 28 | import androidx.compose.ui.text.font.toFontFamily 29 | import androidx.compose.ui.unit.IntOffset 30 | import androidx.compose.ui.unit.sp 31 | import androidx.media3.effect.Crop 32 | import androidx.media3.effect.OverlaySettings 33 | import androidx.media3.effect.TextOverlay 34 | import io.github.devhyper.openvideoeditor.R 35 | import io.github.devhyper.openvideoeditor.misc.ColorPickerSetting 36 | import io.github.devhyper.openvideoeditor.misc.CompatTypefaceSpan 37 | import io.github.devhyper.openvideoeditor.misc.FontPickerSetting 38 | import io.github.devhyper.openvideoeditor.misc.ListDialog 39 | import io.github.devhyper.openvideoeditor.misc.ResizableRectangle 40 | import io.github.devhyper.openvideoeditor.misc.TextfieldSetting 41 | import io.github.devhyper.openvideoeditor.misc.screenToNdc 42 | import io.github.devhyper.openvideoeditor.misc.setFullLengthSpan 43 | import io.github.devhyper.openvideoeditor.misc.spToPx 44 | import io.github.devhyper.openvideoeditor.misc.toOverlayEffect 45 | import io.github.devhyper.openvideoeditor.misc.validateUIntAndNonzero 46 | import kotlinx.coroutines.flow.MutableStateFlow 47 | import kotlinx.coroutines.flow.update 48 | import kotlin.math.roundToInt 49 | 50 | @Composable 51 | fun TextEditor(effectFlow: MutableStateFlow) { 52 | val update = 53 | { offsetX: Float, offsetY: Float, textValue: String, videoWidth: Float, videoHeight: Float, textBackgroundColor: Color, textForegroundColor: Color, textSize: Int, textFontPath: String? -> 54 | effectFlow.update { 55 | { 56 | val overlaySettings = OverlaySettings.Builder() 57 | val x = screenToNdc(offsetX, videoWidth) 58 | val y = screenToNdc(offsetY, videoHeight) 59 | overlaySettings.setOverlayFrameAnchor(1f, -1f) 60 | overlaySettings.setBackgroundFrameAnchor(x, -y) 61 | val spanString = SpannableString(textValue) 62 | spanString.run { 63 | setFullLengthSpan(ForegroundColorSpan(textForegroundColor.toArgb())) 64 | setFullLengthSpan(BackgroundColorSpan(textBackgroundColor.toArgb())) 65 | setFullLengthSpan(AbsoluteSizeSpan(spToPx(textSize.sp.value))) 66 | if (textFontPath != null) { 67 | val typeface = Typeface.createFromFile(textFontPath) 68 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { 69 | setFullLengthSpan(TypefaceSpan(typeface)) 70 | } else { 71 | setFullLengthSpan(CompatTypefaceSpan(typeface)) 72 | } 73 | } 74 | } 75 | TextOverlay.createStaticTextOverlay( 76 | spanString, 77 | overlaySettings.build() 78 | ).toOverlayEffect() 79 | } 80 | } 81 | } 82 | var showListDialog by remember { mutableStateOf(true) } 83 | var textSize by remember { mutableIntStateOf(12) } 84 | var textBackgroundColor by remember { mutableStateOf(Color.Transparent) } 85 | var textForegroundColor by remember { mutableStateOf(Color.Black) } 86 | var textFont: Font? by remember { mutableStateOf(null) } 87 | var textFontPath: String? by remember { mutableStateOf(null) } 88 | if (showListDialog) { 89 | ListDialog( 90 | title = stringResource(R.string.text), 91 | dismissText = stringResource(R.string.cancel), 92 | acceptText = stringResource(R.string.set), 93 | onDismissRequest = { showListDialog = false }, 94 | onAcceptRequest = { 95 | showListDialog = false 96 | } 97 | ) { 98 | item { 99 | TextfieldSetting( 100 | name = stringResource(R.string.size), 101 | onValueChanged = { 102 | val error = validateUIntAndNonzero(it) 103 | textSize = if (error.isEmpty()) { 104 | it.toInt() 105 | } else { 106 | 12 107 | } 108 | error 109 | }) 110 | } 111 | item { 112 | ColorPickerSetting( 113 | name = stringResource(R.string.background_color), 114 | defaultColor = Color.Transparent, 115 | onSelectionChanged = { 116 | textBackgroundColor = it 117 | }) 118 | } 119 | item { 120 | ColorPickerSetting( 121 | name = stringResource(R.string.foreground_color), 122 | defaultColor = Color.Black, 123 | onSelectionChanged = { 124 | textForegroundColor = it 125 | }) 126 | } 127 | item { 128 | FontPickerSetting(stringResource(R.string.font)) { font, path -> 129 | textFont = font 130 | textFontPath = path 131 | } 132 | } 133 | } 134 | } else { 135 | BoxWithConstraints(modifier = Modifier.fillMaxSize()) { 136 | val videoWidth = constraints.maxWidth.toFloat() 137 | val videoHeight = constraints.maxHeight.toFloat() 138 | var offsetX by remember { mutableFloatStateOf(videoWidth / 2f) } 139 | var offsetY by remember { mutableFloatStateOf(videoHeight / 2f) } 140 | var textValue by remember { mutableStateOf("Text") } 141 | val updateFlow = { 142 | update( 143 | offsetX, 144 | offsetY, 145 | textValue, 146 | videoWidth, 147 | videoHeight, 148 | textBackgroundColor, 149 | textForegroundColor, 150 | textSize, 151 | textFontPath 152 | ) 153 | } 154 | updateFlow() 155 | BasicTextField(modifier = Modifier 156 | .absoluteOffset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) } 157 | .pointerInput(Unit) { 158 | detectDragGestures { change, dragAmount -> 159 | change.consume() 160 | if (offsetX + dragAmount.x > 0 && offsetX + dragAmount.x < videoWidth) { 161 | offsetX += dragAmount.x 162 | } 163 | if (offsetY + dragAmount.y > 0 && offsetY + dragAmount.y < videoHeight) { 164 | offsetY += dragAmount.y 165 | } 166 | updateFlow() 167 | } 168 | }, 169 | textStyle = TextStyle.Default.copy( 170 | fontFamily = textFont?.toFontFamily(), 171 | fontSize = textSize.sp, 172 | color = textForegroundColor, 173 | background = textBackgroundColor 174 | ), 175 | value = textValue, 176 | onValueChange = { 177 | textValue = it 178 | updateFlow() 179 | }) 180 | } 181 | } 182 | } 183 | 184 | @Composable 185 | fun CropEditor(effectFlow: MutableStateFlow) { 186 | BoxWithConstraints(modifier = Modifier.fillMaxSize()) { 187 | val videoWidth = constraints.maxWidth.toFloat() 188 | val videoHeight = constraints.maxHeight.toFloat() 189 | ResizableRectangle(videoWidth, videoHeight, 0F, 0F) { width, height, x, y -> 190 | val left = screenToNdc(x, videoWidth) 191 | val right = screenToNdc(x + width, videoWidth) 192 | val bottom = screenToNdc(videoHeight - y - height, videoHeight) 193 | val top = screenToNdc(videoHeight - y, videoHeight) 194 | if (right > left && top > bottom) { 195 | effectFlow.update { 196 | { 197 | Crop( 198 | left, right, bottom, top 199 | ) 200 | } 201 | } 202 | } 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/devhyper/openvideoeditor/videoeditor/CustomFrameworkMuxer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.devhyper.openvideoeditor.videoeditor; 17 | 18 | import static androidx.media3.common.util.Assertions.checkNotNull; 19 | import static androidx.media3.common.util.Assertions.checkState; 20 | import static androidx.media3.common.util.Util.SDK_INT; 21 | import static androidx.media3.common.util.Util.castNonNull; 22 | 23 | import android.annotation.SuppressLint; 24 | import android.media.MediaCodec.BufferInfo; 25 | import android.media.MediaFormat; 26 | import android.media.MediaMuxer; 27 | import android.system.ErrnoException; 28 | 29 | import androidx.annotation.Nullable; 30 | import androidx.media3.common.C; 31 | import androidx.media3.common.Format; 32 | import androidx.media3.common.Metadata; 33 | import androidx.media3.common.MimeTypes; 34 | import androidx.media3.common.util.MediaFormatUtil; 35 | import androidx.media3.common.util.Util; 36 | import androidx.media3.container.Mp4LocationData; 37 | import androidx.media3.muxer.Muxer; 38 | import androidx.media3.transformer.TransformerUtil; 39 | 40 | import com.google.common.collect.ImmutableList; 41 | 42 | import java.io.FileDescriptor; 43 | import java.io.IOException; 44 | import java.lang.reflect.Field; 45 | import java.nio.ByteBuffer; 46 | import java.util.HashMap; 47 | import java.util.Map; 48 | 49 | /** 50 | * {@link Muxer} implementation that uses a {@link MediaMuxer}. 51 | */ 52 | /* package */ final class CustomFrameworkMuxer implements Muxer { 53 | public static final String MUXER_STOPPING_FAILED_ERROR_MESSAGE = "Failed to stop the MediaMuxer"; 54 | 55 | // MediaMuxer supported sample formats are documented in MediaMuxer.addTrack(MediaFormat). 56 | private static final ImmutableList SUPPORTED_VIDEO_SAMPLE_MIME_TYPES = 57 | getSupportedVideoSampleMimeTypes(); 58 | private static final ImmutableList SUPPORTED_AUDIO_SAMPLE_MIME_TYPES = 59 | ImmutableList.of(MimeTypes.AUDIO_AAC, MimeTypes.AUDIO_AMR_NB, MimeTypes.AUDIO_AMR_WB); 60 | 61 | /** 62 | * {@link Muxer.Factory} for {@link CustomFrameworkMuxer}. 63 | */ 64 | public static final class Factory implements Muxer.Factory { 65 | private final long videoDurationMs; 66 | FileDescriptor fd; 67 | 68 | public Factory(long videoDurationMs) { 69 | this.videoDurationMs = videoDurationMs; 70 | } 71 | 72 | public Factory(FileDescriptor fd, long videoDurationMs) { 73 | this.videoDurationMs = videoDurationMs; 74 | this.fd = fd; 75 | } 76 | 77 | @Override 78 | public CustomFrameworkMuxer create(String path) throws MuxerException { 79 | MediaMuxer mediaMuxer; 80 | try { 81 | if (path.isEmpty()) { 82 | mediaMuxer = new MediaMuxer(fd, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 83 | android.system.Os.close(fd); 84 | } else { 85 | mediaMuxer = new MediaMuxer(path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 86 | } 87 | } catch (IOException e) { 88 | throw new MuxerException("Error creating muxer", e); 89 | } catch (ErrnoException e) { 90 | throw new RuntimeException(e); 91 | } 92 | return new CustomFrameworkMuxer(mediaMuxer, videoDurationMs); 93 | } 94 | 95 | @Override 96 | public ImmutableList getSupportedSampleMimeTypes(@C.TrackType int trackType) { 97 | if (trackType == C.TRACK_TYPE_VIDEO) { 98 | return SUPPORTED_VIDEO_SAMPLE_MIME_TYPES; 99 | } else if (trackType == C.TRACK_TYPE_AUDIO) { 100 | return SUPPORTED_AUDIO_SAMPLE_MIME_TYPES; 101 | } 102 | return ImmutableList.of(); 103 | } 104 | } 105 | 106 | private final MediaMuxer mediaMuxer; 107 | private final long videoDurationUs; 108 | private final Map trackTokenToLastPresentationTimeUs; 109 | private final Map trackTokenToPresentationTimeOffsetUs; 110 | 111 | @Nullable 112 | private TrackToken videoTrackToken; 113 | 114 | private boolean isStarted; 115 | private boolean isReleased; 116 | 117 | private CustomFrameworkMuxer(MediaMuxer mediaMuxer, long videoDurationMs) { 118 | this.mediaMuxer = mediaMuxer; 119 | this.videoDurationUs = Util.msToUs(videoDurationMs); 120 | trackTokenToLastPresentationTimeUs = new HashMap<>(); 121 | trackTokenToPresentationTimeOffsetUs = new HashMap<>(); 122 | } 123 | 124 | @Override 125 | public TrackToken addTrack(Format format) throws MuxerException { 126 | String sampleMimeType = checkNotNull(format.sampleMimeType); 127 | MediaFormat mediaFormat; 128 | boolean isVideo = MimeTypes.isVideo(sampleMimeType); 129 | if (isVideo) { 130 | mediaFormat = MediaFormat.createVideoFormat(sampleMimeType, format.width, format.height); 131 | MediaFormatUtil.maybeSetColorInfo(mediaFormat, format.colorInfo); 132 | try { 133 | mediaMuxer.setOrientationHint(format.rotationDegrees); 134 | } catch (RuntimeException e) { 135 | throw new MuxerException( 136 | "Failed to set orientation hint with rotationDegrees=" + format.rotationDegrees, e); 137 | } 138 | } else { 139 | mediaFormat = 140 | MediaFormat.createAudioFormat(sampleMimeType, format.sampleRate, format.channelCount); 141 | MediaFormatUtil.maybeSetString(mediaFormat, MediaFormat.KEY_LANGUAGE, format.language); 142 | } 143 | MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData); 144 | int trackIndex; 145 | try { 146 | trackIndex = mediaMuxer.addTrack(mediaFormat); 147 | } catch (RuntimeException e) { 148 | throw new MuxerException("Failed to add track with format=" + format, e); 149 | } 150 | 151 | TrackToken trackToken = new TrackTokenImpl(trackIndex); 152 | if (isVideo) { 153 | videoTrackToken = trackToken; 154 | } 155 | 156 | return trackToken; 157 | } 158 | 159 | @Override 160 | public void writeSampleData(TrackToken trackToken, ByteBuffer data, BufferInfo bufferInfo) 161 | throws MuxerException { 162 | long presentationTimeUs = bufferInfo.presentationTimeUs; 163 | if (videoDurationUs != C.TIME_UNSET 164 | && trackToken == videoTrackToken 165 | && presentationTimeUs > videoDurationUs) { 166 | return; 167 | } 168 | if (!isStarted) { 169 | if (Util.SDK_INT < 30 && presentationTimeUs < 0) { 170 | trackTokenToPresentationTimeOffsetUs.put(trackToken, -presentationTimeUs); 171 | } 172 | startMuxer(); 173 | } 174 | 175 | long presentationTimeOffsetUs = 176 | trackTokenToPresentationTimeOffsetUs.containsKey(trackToken) 177 | ? trackTokenToPresentationTimeOffsetUs.get(trackToken) 178 | : 0; 179 | presentationTimeUs += presentationTimeOffsetUs; 180 | 181 | long lastSamplePresentationTimeUs = 182 | trackTokenToLastPresentationTimeUs.containsKey(trackToken) 183 | ? trackTokenToLastPresentationTimeUs.get(trackToken) 184 | : 0; 185 | // writeSampleData blocks on old API versions, so check here to avoid calling the method. 186 | checkState( 187 | Util.SDK_INT > 24 || presentationTimeUs >= lastSamplePresentationTimeUs, 188 | "Samples not in presentation order (" 189 | + presentationTimeUs 190 | + " < " 191 | + lastSamplePresentationTimeUs 192 | + ") unsupported on this API version"); 193 | trackTokenToLastPresentationTimeUs.put(trackToken, presentationTimeUs); 194 | 195 | checkState( 196 | presentationTimeOffsetUs == 0 || presentationTimeUs >= lastSamplePresentationTimeUs, 197 | "Samples not in presentation order (" 198 | + presentationTimeUs 199 | + " < " 200 | + lastSamplePresentationTimeUs 201 | + ") unsupported when using negative PTS workaround"); 202 | bufferInfo.set(bufferInfo.offset, bufferInfo.size, presentationTimeUs, bufferInfo.flags); 203 | 204 | try { 205 | checkState(trackToken instanceof TrackTokenImpl); 206 | mediaMuxer.writeSampleData(((TrackTokenImpl) trackToken).trackIndex, data, bufferInfo); 207 | } catch (RuntimeException e) { 208 | throw new MuxerException( 209 | "Failed to write sample for presentationTimeUs=" 210 | + presentationTimeUs 211 | + ", size=" 212 | + bufferInfo.size, 213 | e); 214 | } 215 | } 216 | 217 | @Override 218 | public void addMetadataEntry(Metadata.Entry metadataEntry) { 219 | if (metadataEntry instanceof Mp4LocationData) { 220 | mediaMuxer.setLocation( 221 | ((Mp4LocationData) metadataEntry).latitude, ((Mp4LocationData) metadataEntry).longitude); 222 | } 223 | } 224 | 225 | @Override 226 | public void close() throws MuxerException { 227 | if (isReleased) { 228 | return; 229 | } 230 | 231 | if (!isStarted) { 232 | // Start the muxer even if no samples have been written so that it throws instead of silently 233 | // writing nothing to the output file. 234 | startMuxer(); 235 | } 236 | 237 | if (videoDurationUs != C.TIME_UNSET && videoTrackToken != null) { 238 | BufferInfo bufferInfo = new BufferInfo(); 239 | bufferInfo.set( 240 | /* newOffset= */ 0, 241 | /* newSize= */ 0, 242 | videoDurationUs, 243 | TransformerUtil.getMediaCodecFlags(C.BUFFER_FLAG_END_OF_STREAM)); 244 | writeSampleData(checkNotNull(videoTrackToken), ByteBuffer.allocateDirect(0), bufferInfo); 245 | } 246 | 247 | isStarted = false; 248 | try { 249 | stopMuxer(mediaMuxer); 250 | } catch (RuntimeException e) { 251 | throw new MuxerException(MUXER_STOPPING_FAILED_ERROR_MESSAGE, e); 252 | } finally { 253 | mediaMuxer.release(); 254 | isReleased = true; 255 | } 256 | } 257 | 258 | private void startMuxer() throws MuxerException { 259 | try { 260 | mediaMuxer.start(); 261 | } catch (RuntimeException e) { 262 | throw new MuxerException("Failed to start the muxer", e); 263 | } 264 | isStarted = true; 265 | } 266 | 267 | // Accesses MediaMuxer state via reflection to ensure that muxer resources can be released even 268 | // if stopping fails. 269 | @SuppressLint("PrivateApi") 270 | private static void stopMuxer(MediaMuxer mediaMuxer) { 271 | try { 272 | mediaMuxer.stop(); 273 | } catch (RuntimeException e) { 274 | if (SDK_INT < 30) { 275 | // Set the muxer state to stopped even if mediaMuxer.stop() failed so that 276 | // mediaMuxer.release() doesn't attempt to stop the muxer and therefore doesn't throw the 277 | // same exception without releasing its resources. This is already implemented in MediaMuxer 278 | // from API level 30. See also b/80338884. 279 | try { 280 | Field muxerStoppedStateField = MediaMuxer.class.getDeclaredField("MUXER_STATE_STOPPED"); 281 | muxerStoppedStateField.setAccessible(true); 282 | int muxerStoppedState = castNonNull((Integer) muxerStoppedStateField.get(mediaMuxer)); 283 | Field muxerStateField = MediaMuxer.class.getDeclaredField("mState"); 284 | muxerStateField.setAccessible(true); 285 | muxerStateField.set(mediaMuxer, muxerStoppedState); 286 | } catch (Exception reflectionException) { 287 | // Do nothing. 288 | } 289 | } 290 | // Rethrow the original error. 291 | throw e; 292 | } 293 | } 294 | 295 | private static ImmutableList getSupportedVideoSampleMimeTypes() { 296 | ImmutableList.Builder supportedMimeTypes = 297 | new ImmutableList.Builder() 298 | .add(MimeTypes.VIDEO_H264, MimeTypes.VIDEO_H263, MimeTypes.VIDEO_MP4V); 299 | if (SDK_INT >= 24) { 300 | supportedMimeTypes.add(MimeTypes.VIDEO_H265); 301 | } 302 | if (SDK_INT >= 34) { 303 | supportedMimeTypes.add(MimeTypes.VIDEO_AV1); 304 | } 305 | return supportedMimeTypes.build(); 306 | } 307 | 308 | private static class TrackTokenImpl implements TrackToken { 309 | public final int trackIndex; 310 | 311 | public TrackTokenImpl(int trackIndex) { 312 | this.trackIndex = trackIndex; 313 | } 314 | } 315 | } 316 | --------------------------------------------------------------------------------