{"id":49323809,"url":"https://github.com/steliyanh/kadr","last_synced_at":"2026-04-28T21:01:07.035Z","repository":{"id":353954601,"uuid":"1218020461","full_name":"SteliyanH/kadr","owner":"SteliyanH","description":"Declarative video composition for Apple platforms — compose, transform, and export with a result-builder DSL and async/await.","archived":false,"fork":false,"pushed_at":"2026-04-27T17:43:14.000Z","size":39876,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-27T20:29:18.292Z","etag":null,"topics":["avfoundation","ios","macos","result-builder","swift","swift-package","swiftui","video","video-editing"],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SteliyanH.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-22T13:03:34.000Z","updated_at":"2026-04-27T17:42:05.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/SteliyanH/kadr","commit_stats":null,"previous_names":["steliyanh/kadr"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/SteliyanH/kadr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SteliyanH%2Fkadr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SteliyanH%2Fkadr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SteliyanH%2Fkadr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SteliyanH%2Fkadr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SteliyanH","download_url":"https://codeload.github.com/SteliyanH/kadr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SteliyanH%2Fkadr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32399010,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-28T19:38:08.556Z","status":"ssl_error","status_checked_at":"2026-04-28T19:37:55.688Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["avfoundation","ios","macos","result-builder","swift","swift-package","swiftui","video","video-editing"],"created_at":"2026-04-26T19:01:37.378Z","updated_at":"2026-04-28T21:01:06.947Z","avatar_url":"https://github.com/SteliyanH.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Kadr\n\n[![CI](https://github.com/SteliyanH/kadr/actions/workflows/ci.yml/badge.svg)](https://github.com/SteliyanH/kadr/actions/workflows/ci.yml)\n[![Swift 6.0](https://img.shields.io/badge/Swift-6.0-orange.svg)](https://swift.org)\n[![Platforms](https://img.shields.io/badge/Platforms-iOS%2016+%20|%20macOS%2013+%20|%20tvOS%2016+%20|%20visionOS%201+-blue.svg)](https://developer.apple.com)\n[![License](https://img.shields.io/badge/License-Apache%202.0-green.svg)](LICENSE)\n\n**SwiftUI for video. Compose, transform, export — in Swift you actually want to write.**\n\nA modern, declarative Swift library for video composition on Apple platforms. Build videos using a result-builder DSL with async/await throughout.\n\n## Quick Start\n\n```swift\nimport Kadr\n\nlet url = try await Video {\n    ImageClip(heroImage, duration: 5.0)\n}\n.audio(url: musicURL)\n.export(to: outputURL)\n```\n\n## Why Kadr?\n\nFFmpegKit retired in January 2025. Pixel SDK sunset in February 2025. AVFoundation is powerful but verbose. The Swift video ecosystem needs a modern, native, declarative library.\n\n**7 imperative functions become 3 DSL primitives + modifiers:**\n\n| Before (imperative) | After (Kadr) |\n|---|---|\n| `generate(.single, image, audio)` | `Video { ImageClip(img) }.audio(url:).export(to:)` |\n| `mergeMovies(videoURLs:)` | `Video { urls.map { VideoClip(url: $0) } }.export(to:)` |\n| `reverseVideo(fromVideo:)` | `Video { VideoClip(url:).reversed() }.export(to:)` |\n| `splitVideo(at:)` | `Video { VideoClip(url:).trimmed(to: 5...20) }.export(to:)` |\n| `mergeVideoWithAudio(...)` | `Video { VideoClip(url:).muted() }.audio(url:).export(to:)` |\n\n## Comparison\n\n| | Kadr | AVFoundation (raw) | VideoLab | FFmpegKit |\n|---|---|---|---|---|\n| **API style** | Declarative DSL | Imperative | Layer-based | CLI wrapper |\n| **Swift concurrency** | async/await native | Callbacks | No | No |\n| **Swift 6 / Sendable** | Full strict concurrency | Partial | No | No |\n| **Maintained (2026)** | Active | Apple (low-level) | Inactive | Retired (Jan 2025) |\n| **Dependencies** | None (AVFoundation only) | N/A | None | FFmpeg binary |\n| **Learning curve** | Minutes | Hours | Hours | Moderate |\n| **License** | Apache 2.0 | Proprietary | MIT | LGPL |\n\n## Features\n\n### v0.6.0 (current — `0.6.0`)\n\n- **Multi-track timeline.** Hybrid DSL: top-level clips chain implicitly (v0.5 unchanged); `.at(time:)` pins a clip to an explicit composition time as a free-floating parallel track; `Track { ... }` groups clips into a parallel sub-timeline anchored at `Track(at:)`. Layer ordering is declaration order — later renders on top.\n- **Multi-input compositors.** `MultiInputCompositor` protocol (separate from v0.5's single-input `Compositor`) — `func process(images: [CIImage], context:) -\u003e CIImage`. Attach via `Video.compositor(_:)`. Default behavior is alpha-composite later-over-earlier; custom blends run via a `KadrVideoCompositor` (custom `AVVideoCompositing` implementation).\n- **Transitions inside Tracks** and **nested Tracks** via recursive pre-render. Mirrors the `FilterProcessor` pattern — Tracks containing transitions or nested Tracks are pre-rendered to a temp `.mp4` then inserted as a single piece on the parent's parallel video track.\n\n### v0.5.0 (`0.5.0`)\n\n- **Time-ranged overlay visibility**: `.visible(during: CMTimeRange)` / `.visible(during: ClosedRange\u003cTimeInterval\u003e)` on every overlay type — overlays render only during a portion of the composition.\n- **LUTs**: `Filter.lut(LUT)` and the throwing factory `Filter.lut(url:)` for `.cube` 3D color-grading files. Standalone `LUT` value type loads + parses once for reuse across clips.\n- **Chroma key**: `Filter.chromaKey(color:threshold:)` and the standalone `ChromaKey` value type. ITU-R BT.601 chroma distance, programmatic `CIColorCube` cube.\n- **Custom compositors** *(foundation)*: public `Compositor` protocol + closure form, plus `CompositorContext` carrying per-frame `time` + `renderSize`. Plugs into the engine's existing per-clip pre-render pass.\n- **Per-clip crop**: `VideoClip.crop(at:size:anchor:)` mirroring the composition-wide `Video.crop`. Built as a thin `Compositor`.\n- **Alpha-mask crop**: `VideoClip.mask(_: CIImage)` / `mask(_: PlatformImage)` for non-rectangular shapes via `CIBlendWithAlphaMask`. Built as a thin `Compositor`.\n\n### v0.4.1 (`0.4.1`)\n\n- **Clip identity**: `ClipID` (string-backed, mirrors `LayerID`). Assign with `.id(_:)` on `VideoClip`, `ImageClip`, `TitleSequence`. IDs survive the existing modifier chain (`.trimmed`, `.reversed`, `.speed`, `.filter`, etc.) so callers can address clips across reorders and trims — driven by [`kadr-ui`](https://github.com/SteliyanH/kadr-ui)'s timeline component.\n\n### v0.4.0 (`0.4.0`)\n\n- **Composition introspection**: `Video.clips`, `overlays`, `audioTracks`, `preset`, and `crop` are publicly readable so callers can build their own timeline / preview / hit-testing UI without re-deriving state. Per-clip storage on `VideoClip`, `ImageClip`, and `AudioTrack` is also publicly readable.\n- **Preview**: `Video.makePlayerItem()` returns an `AVPlayerItem` with the composition's videoComposition (preset, crop, transitions) and audioMix (background music, fades, ducking) pre-attached, ready for `AVKit.VideoPlayer`. `Video.thumbnail(at:)` renders a single composition frame.\n- **Layout helpers**: `Layout.resolveFrame(position:size:anchor:in:)` mirrors the engine's coordinate math so custom UI can hit-test overlays in pixel-exact alignment with what the engine renders.\n- **Companion package**: [`kadr-ui`](https://github.com/SteliyanH/kadr-ui) is a separate SwiftUI components package (`VideoPreview`, `TimelineView`, `ThumbnailStrip`, gesture handlers) consuming these primitives.\n\n### v0.3 (`0.3.0`)\n\n- **Layout primitives**: `Position` (`.normalized` / `.pixels` / `.percent` plus 9 named anchors), `Size` (with `.aspectFit` / `.aspectFill`), `Anchor`, and `LayerID`\n- **Overlays**: `ImageOverlay`, `TextOverlay` + `TextStyle`, `StickerOverlay` (with `.shadow` and `.rotation` modifiers), and `Video.watermark(...)` sugar\n- **Filters**: `VideoClip.filter(_:)` with built-in `CIFilter` presets — `.brightness`, `.contrast`, `.saturation`, `.exposure`, `.sepia`, `.mono`. Variadic and chainable.\n- **Crop**: `Video.crop(at:size:anchor:)` — composition-wide rectangular crop sharing the layout coordinate system\n- **Sugar**: `BackgroundMusic` (defaults: volume 0.6, fades, ducking), `TitleSequence` (text title clip with cross-platform rendering), `Timecode` (SMPTE `HH:MM:SS:FF` format/parse)\n\n### v0.2\n\n- **Transitions**: `.fade` (through black), `.dissolve` (cross-blend), `.slide` (4 directions) — wired through the engine with audio crossfades\n- **Speed control**: `VideoClip.speed(_:)` — `0.25...4.0`, pitch-preserving\n- **Audio ducking**: `AudioTrack.ducking(_:)` — auto-lowers music while clip audio plays\n- **Frame-accurate timing**: every time-related API accepts `CMTime` for frame-precise edits, with `TimeInterval` overloads for ergonomic call sites\n\n### v0.1\n\n- Result-builder DSL (`Video { ... }`)\n- `ImageClip` and `VideoClip` primitives\n- `AudioTrack` with `.volume(_:)`, `.fadeIn(_:)`, `.fadeOut(_:)`\n- Clip modifiers: `.trimmed(to:)`, `.reversed()`, `.muted()`, `.withAudio(_:)`\n- Export presets: `.reelsAndShorts`, `.tiktok`, `.square`, `.cinema`, `.custom(...)`\n- H.264 and HEVC codec support\n- Progress reporting via `AsyncThrowingStream` with time estimation\n- Thumbnail extraction: `VideoClip.thumbnail(at:)`\n- Video metadata: duration, resolution, frame rate\n- Typed errors via `KadrError`\n- Export cancellation support\n\n### Roadmap\n\nSee [ROADMAP.md](ROADMAP.md) for the full version plan.\n\n## Examples\n\n```swift\n// Slideshow with background music\nlet url = try await Video {\n    ImageClip(photo1)\n    ImageClip(photo2)\n    ImageClip(photo3)\n}\n.audio(url: musicURL)\n.export(to: outputURL)\n\n// Merge and trim video clips for Reels\nlet url = try await Video {\n    VideoClip(url: clip1URL).trimmed(to: 0...10)\n    VideoClip(url: clip2URL).trimmed(to: 5...15)\n}\n.preset(.reelsAndShorts)\n.export(to: outputURL)\n\n// Replace audio on a video\nlet url = try await Video {\n    VideoClip(url: originalURL).muted()\n}\n.audio(url: newSoundtrackURL)\n.export(to: outputURL)\n\n// Transitions, slow-mo, and ducking music (v0.2)\nlet url = try await Video {\n    VideoClip(url: introURL).trimmed(to: 0...3)\n    Transition.dissolve(duration: 0.5)\n    VideoClip(url: actionURL).trimmed(to: 0...4).speed(0.5)  // half-speed slow-mo\n    Transition.slide(direction: .fromRight, duration: 0.4)\n    VideoClip(url: outroURL).trimmed(to: 0...3)\n}\n.audio { AudioTrack(url: musicURL).volume(0.8).ducking(0.2) }  // music dips when clips speak\n.export(to: outputURL)\n\n// Title card, color-graded clip, watermark, and music (v0.3)\nlet url = try await Video {\n    TitleSequence(\"MY MOVIE\",\n                  duration: 2.0,\n                  style: TextStyle(fontSize: 96, alignment: .center, weight: .bold))\n    Transition.fade(duration: 0.5)\n    VideoClip(url: clipURL).trimmed(to: 0...10)\n        .filter(.brightness(0.05), .contrast(1.1), .saturation(1.2))\n}\n.overlay(\n    TextOverlay(\"LOCATION: HQ\", style: TextStyle(fontSize: 40, weight: .medium))\n        .position(.bottom)\n        .anchor(.bottom)\n)\n.watermark(logo, position: .topRight, opacity: 0.5)\n.crop(at: .center, size: .normalized(width: 0.9, height: 0.9))\n.backgroundMusic(url: musicURL)  // defaults: 60% volume, fades, ducking\n.export(to: outputURL)\n\n// Export with progress tracking\nlet exporter = Video {\n    VideoClip(url: longVideoURL)\n}\n.preset(.cinema)\n.exporter(to: outputURL)\n\nfor try await progress in exporter.run() {\n    print(\"\\(Int(progress.fractionCompleted * 100))%\")\n}\n```\n\n## Installation\n\n### Swift Package Manager\n\nAdd to your `Package.swift`:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/SteliyanH/kadr.git\", from: \"0.1.0\")\n]\n```\n\nOr in Xcode: File \u003e Add Package Dependencies \u003e enter the repository URL.\n\n**Requires:** Xcode 16+ / Swift 6.0+\n\n## Platform Support\n\n| Platform | Minimum Version |\n|---|---|\n| iOS | 16.0 |\n| macOS | 13.0 |\n| tvOS | 16.0 |\n| visionOS | 1.0 |\n\n## Architecture\n\nKadr separates the public DSL from the internal engine:\n\n- **DSL layer** — `Video`, `ImageClip`, `VideoClip`, `AudioTrack`, `Preset`, `Exporter` (public)\n- **Engine layer** — `ImageEncoder`, `CompositionBuilder`, `ExportEngine` (internal, uses AVFoundation)\n\nThe DSL is the stable public API. The engine is the implementation detail that can be refactored without breaking semver.\n\n## Contributing\n\nContributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.\n\n## License\n\nApache 2.0 — see [LICENSE](LICENSE) for details.\n\nApache 2.0 was chosen over MIT for its explicit patent grant, which is relevant for video processing code that touches codec patents (H.264, HEVC).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsteliyanh%2Fkadr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsteliyanh%2Fkadr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsteliyanh%2Fkadr/lists"}