Skip to content

Commit a5b3ca0

Browse files
committed
Pass render time in environment value (#1)
1 parent 590101c commit a5b3ca0

19 files changed

+311
-109
lines changed

.gitignore

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.DS_Store
2+
.build/
3+
/.swiftpm
4+
/Packages
5+
xcuserdata/
6+
DerivedData/
7+
.swiftpm/configuration/registries.json
8+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9+
.netrc
10+

Config.xcconfig

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
MARKETING_VERSION = 0.0.1
2-
CURRENT_PROJECT_VERSION = 1
1+
MARKETING_VERSION = 0.0.2
2+
CURRENT_PROJECT_VERSION = 2

Package.swift

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// swift-tools-version: 6.0
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "SwiftUIFX",
7+
platforms: [.macOS(.v11)],
8+
products: [.library(name: "SwiftUIFX", type: .dynamic, targets: ["SwiftUIFX"])],
9+
targets: [.target(name: "SwiftUIFX")]
10+
)
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import CoreMedia
2+
3+
extension CMTime: Codable {
4+
public init(from decoder: Decoder) throws {
5+
let container = try decoder.container(keyedBy: CodingKeys.self)
6+
let value = try container.decode(CMTimeValue.self, forKey: .value)
7+
let timescale = try container.decode(CMTimeScale.self, forKey: .timescale)
8+
let flags = try CMTimeFlags(rawValue: container.decode(UInt32.self, forKey: .flags))
9+
let epoch = try container.decode(CMTimeEpoch.self, forKey: .epoch)
10+
self.init(value: value, timescale: timescale, flags: flags, epoch: epoch)
11+
}
12+
13+
public func encode(to encoder: Encoder) throws {
14+
var container = encoder.container(keyedBy: CodingKeys.self)
15+
try container.encode(value, forKey: .value)
16+
try container.encode(timescale, forKey: .timescale)
17+
try container.encode(flags.rawValue, forKey: .flags)
18+
try container.encode(epoch, forKey: .epoch)
19+
}
20+
21+
private enum CodingKeys: CodingKey {
22+
case value
23+
case timescale
24+
case flags
25+
case epoch
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import CoreMedia
2+
3+
extension CMTimeRange: Codable {
4+
public init(from decoder: Decoder) throws {
5+
let container = try decoder.container(keyedBy: CodingKeys.self)
6+
let start = try container.decode(CMTime.self, forKey: .start)
7+
let duration = try container.decode(CMTime.self, forKey: .duration)
8+
self.init(start: start, duration: duration)
9+
}
10+
11+
public func encode(to encoder: Encoder) throws {
12+
var container = encoder.container(keyedBy: CodingKeys.self)
13+
try container.encode(start, forKey: .start)
14+
try container.encode(duration, forKey: .duration)
15+
}
16+
17+
private enum CodingKeys: CodingKey {
18+
case start
19+
case duration
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import CoreMedia
2+
3+
extension FxTimingAPI_v4 {
4+
func timelineTime(fromInputTime inputTime: CMTime) -> CMTime {
5+
var timelineTime: CMTime = .zero
6+
self.timelineTime(&timelineTime, fromInputTime: inputTime)
7+
return timelineTime
8+
}
9+
10+
var inPointTimeOfTimeline: CMTime {
11+
var inPointTime: CMTime = .zero
12+
self.inPointTimeOfTimeline(forEffect: &inPointTime)
13+
return inPointTime
14+
}
15+
16+
var outPointTimeOfTimeline: CMTime {
17+
var outPointTime: CMTime = .zero
18+
self.outPointTimeOfTimeline(forEffect: &outPointTime)
19+
return outPointTime
20+
}
21+
22+
var startTime: CMTime {
23+
var startTime: CMTime = .zero
24+
self.startTime(forEffect: &startTime)
25+
return startTime
26+
}
27+
28+
var duration: CMTime {
29+
var duration: CMTime = .zero
30+
durationTime(forEffect: &duration)
31+
return duration
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
extension PROAPIAccessing {
2+
var parameterCreationAPI: FxParameterCreationAPI_v5 {
3+
api(for: FxParameterCreationAPI_v5.self) as! FxParameterCreationAPI_v5
4+
}
5+
6+
var parameterRetrievalAPI: FxParameterRetrievalAPI_v6 {
7+
api(for: FxParameterRetrievalAPI_v6.self) as! FxParameterRetrievalAPI_v6
8+
}
9+
10+
var timingAPI: FxTimingAPI_v4 {
11+
api(for: FxTimingAPI_v4.self) as! FxTimingAPI_v4
12+
}
13+
}

Plugin/Generators/SwiftUIViewGenerator.swift

+39-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import SwiftUI
2+
import SwiftUIFX
23

34
// MARK: - SwiftUIViewGenerator
45

@@ -13,14 +14,14 @@ import SwiftUI
1314
}
1415

1516
override func addParameters() throws {
16-
parameterCreationAPI.addStringParameter(
17+
apiManager.parameterCreationAPI.addStringParameter(
1718
withName: "Package Path (Drag & Drop)",
1819
parameterID: 1,
1920
defaultValue: "",
2021
parameterFlags: FxParameterFlags(kFxParameterFlag_DEFAULT)
2122
)
2223

23-
parameterCreationAPI.addPushButton(
24+
apiManager.parameterCreationAPI.addPushButton(
2425
withName: "Compile",
2526
parameterID: 2,
2627
selector: #selector(SwiftUIViewGenerator.compile),
@@ -32,15 +33,38 @@ import SwiftUI
3233
compile()
3334
}
3435

36+
override func pluginState(
37+
at renderTime: CMTime,
38+
quality _: UInt
39+
) throws -> Data? {
40+
try JSONEncoder().encode(State(
41+
timelineTime: apiManager.timingAPI.timelineTime(fromInputTime: renderTime),
42+
timelineTimeRange: .init(
43+
start: apiManager.timingAPI.inPointTimeOfTimeline,
44+
end: apiManager.timingAPI.outPointTimeOfTimeline
45+
),
46+
generatorTimeRange: .init(
47+
start: apiManager.timingAPI.timelineTime(fromInputTime: apiManager.timingAPI.startTime),
48+
duration: apiManager.timingAPI.timelineTime(fromInputTime: apiManager.timingAPI.duration)
49+
)
50+
))
51+
}
52+
3553
override func renderDestinationImage(
3654
sourceImages: [CIImage],
37-
pluginState _: Data?,
55+
pluginState: Data?,
3856
at _: CMTime
39-
) -> CIImage {
57+
) throws -> CIImage {
4058
guard let view else { return .clear }
59+
60+
let state = try JSONDecoder().decode(State.self, from: pluginState ?? Data())
61+
4162
return DispatchQueue.main.sync {
4263
let renderer = ImageRenderer(
4364
content: view
65+
.environment(\.timelineTime, state.timelineTime)
66+
.environment(\.timelineTimeRange, state.timelineTimeRange)
67+
.environment(\.generatorTimeRange, state.generatorTimeRange)
4468
.frame(width: sourceImages[0].extent.width, height: sourceImages[0].extent.height)
4569
)
4670
renderer.proposedSize = .init(sourceImages[0].extent.size)
@@ -53,10 +77,20 @@ import SwiftUI
5377
private var view: AnyView?
5478
}
5579

80+
// MARK: SwiftUIViewGenerator.State
81+
82+
extension SwiftUIViewGenerator {
83+
struct State: Codable {
84+
let timelineTime: CMTime
85+
let timelineTimeRange: CMTimeRange
86+
let generatorTimeRange: CMTimeRange
87+
}
88+
}
89+
5690
extension SwiftUIViewGenerator {
5791
@objc func compile() {
5892
var packagePath: NSString = ""
59-
parameterRetrievalAPI.getStringParameterValue(&packagePath, fromParameter: 1)
93+
apiManager.parameterRetrievalAPI.getStringParameterValue(&packagePath, fromParameter: 1)
6094
let package = URL(filePath: packagePath as String)
6195

6296
guard !(packagePath as String).isEmpty else { return }

Plugin/TileableEffect.swift

+21-14
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,20 @@ class TileableEffect: NSObject, FxTileableEffect {
1111

1212
// MARK: Internal
1313

14+
let apiManager: PROAPIAccessing
15+
1416
var properties: [String: Any] {
1517
[
1618
kFxPropertyKey_MayRemapTime: false,
1719
kFxPropertyKey_VariesWhenParamsAreStatic: false
1820
]
1921
}
2022

21-
var parameterCreationAPI: FxParameterCreationAPI_v5 {
22-
apiManager.api(for: FxParameterCreationAPI_v5.self) as! FxParameterCreationAPI_v5
23-
}
24-
25-
var parameterRetrievalAPI: FxParameterRetrievalAPI_v6 {
26-
apiManager.api(for: FxParameterRetrievalAPI_v6.self) as! FxParameterRetrievalAPI_v6
23+
func pluginState(
24+
at _: CMTime,
25+
quality _: UInt
26+
) throws -> Data? {
27+
nil
2728
}
2829

2930
func destinationImageRect(
@@ -49,13 +50,12 @@ class TileableEffect: NSObject, FxTileableEffect {
4950
sourceImages: [CIImage],
5051
pluginState _: Data?,
5152
at _: CMTime
52-
) -> CIImage {
53+
) throws -> CIImage {
5354
sourceImages.first ?? .clear
5455
}
5556

5657
// MARK: Private
5758

58-
private let apiManager: PROAPIAccessing
5959
private let context = CIContext()
6060
}
6161

@@ -70,11 +70,18 @@ extension TileableEffect {
7070
properties?.pointee = self.properties as NSDictionary
7171
}
7272

73-
func pluginState(
74-
_: AutoreleasingUnsafeMutablePointer<NSData>?,
75-
at _: CMTime,
76-
quality _: UInt
77-
) throws {}
73+
final func pluginState(
74+
_ pluginState: AutoreleasingUnsafeMutablePointer<NSData>?,
75+
at renderTime: CMTime,
76+
quality qualityLevel: UInt
77+
) throws {
78+
if let state = try self.pluginState(
79+
at: renderTime,
80+
quality: qualityLevel
81+
) {
82+
pluginState?.pointee = state as NSData
83+
}
84+
}
7885

7986
final func destinationImageRect(
8087
_ destinationImageRect: UnsafeMutablePointer<FxRect>,
@@ -122,7 +129,7 @@ extension TileableEffect {
122129
)
123130
}
124131

125-
var renderedImage = renderDestinationImage(
132+
var renderedImage = try renderDestinationImage(
126133
sourceImages: sourceImages,
127134
pluginState: pluginState,
128135
at: renderTime

README.md

+31
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,34 @@ struct MyView: View {
4242
6. Select the generator, navigate to the generator inspector, and drag the top level directory of your Swift package into the "Package Path" text field.
4343
7. Press the "Compile" button to compile your view. It should now be displayed in your video timeline.
4444
8. Any time you make a change to your package you will need to press the "Compile" button again.
45+
46+
### Environment Values
47+
48+
To access environment values in your SwiftUI view:
49+
1. Add SwiftUIFX as a dependency of your package.
50+
```swift
51+
let package = Package(
52+
name: "MyVideoOverlay",
53+
platforms: [.macOS(.v13)],
54+
products: [.library(name: "MyVideoOverlay", type: .dynamic, targets: ["MyVideoOverlay"])],
55+
dependencies: [.package(url: "https://github.com/finnvoor/SwiftUIFX.git", branch: "main")],
56+
targets: [.target(name: "MyVideoOverlay", dependencies: [.product(name: "SwiftUIFX", package: "SwiftUIFX")])]
57+
)
58+
```
59+
2. Import SwiftUIFX in your SwiftUI view.
60+
```swift
61+
import SwiftUIFX
62+
```
63+
3. Access the environment value using the `Environment` property wrapper.
64+
```swift
65+
@Environment(\.timelineTime) var timelineTime: CMTime
66+
@Environment(\.timelineTimeRange) var timelineTimeRange: CMTimeRange
67+
@Environment(\.generatorTimeRange) var generatorTimeRange: CMTimeRange
68+
```
69+
70+
### Development
71+
72+
1. Clone the repository.
73+
2. Run `swift build -c release --arch arm64 --arch x86_64` at the top level of the repository.
74+
3. Change the code sign identity in the build script phases of Plugin ("Copy and Code Sign FxPlug.framework" and "Copy and Code Sign PluginManager.framework").
75+
4. Open `SwiftUIFX.xcodeproj`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import CoreMedia
2+
import SwiftUI
3+
4+
public extension EnvironmentValues {
5+
@Entry var timelineTime: CMTime = .zero
6+
@Entry var timelineTimeRange: CMTimeRange = .zero
7+
@Entry var generatorTimeRange: CMTimeRange = .zero
8+
}

0 commit comments

Comments
 (0)