Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
0e1ac30
First step to marker support - some test data borrowed from OTIO Core…
vade Jul 1, 2025
b954b1b
Draft of marker view
vade Jul 1, 2025
9341ab2
Wire basic marker view up - need to validate placement logic
vade Jul 2, 2025
e5b5ccd
Fix marker position on main stack
vade Jul 2, 2025
5e1907c
Better marker drawing
vade Jul 2, 2025
feaf3bc
Xleanup
vade Jul 2, 2025
5ddfecf
more tweaks
vade Jul 2, 2025
fb3f35e
Nicer marker drawing
vade Jul 2, 2025
db33aab
Cleanup
vade Jul 2, 2025
ec64360
Fix alignment of TimelineRuler canvas to allow for "overdraw" fixing #25
vade Jul 2, 2025
54a979d
First step to marker support - some test data borrowed from OTIO Core…
vade Jul 1, 2025
f06acad
Draft of marker view
vade Jul 1, 2025
883836b
Wire basic marker view up - need to validate placement logic
vade Jul 2, 2025
54f871f
Fix marker position on main stack
vade Jul 2, 2025
fca8c66
Better marker drawing
vade Jul 2, 2025
432011b
Xleanup
vade Jul 2, 2025
800bd79
more tweaks
vade Jul 2, 2025
170009b
Nicer marker drawing
vade Jul 2, 2025
ad8bba6
Cleanup
vade Jul 2, 2025
3b30da9
Fix alignment of TimelineRuler canvas to allow for "overdraw" fixing #25
vade Jul 2, 2025
3c7cc4d
Fix error
vade Jul 2, 2025
f5c2125
Merge branch 'feature/marker-support' of https://github.com/Synopsis/…
vade Jul 3, 2025
4a0ae69
Update README.md
vade Oct 3, 2024
79c5917
We dont need @State as we arent editing content in the OTIO TImeline,…
vade Oct 24, 2024
75c35ce
Same here
vade Oct 24, 2024
773cbd1
Add scrollview optimization
vade Oct 24, 2024
835c252
Add placeholder media which we can add to our composition if we cant …
vade Oct 24, 2024
4702194
Media
vade Oct 24, 2024
4269666
Fix case where OTIO references TC that is not available and calculate…
vade Oct 24, 2024
6083439
Reverse index video to match NLE track standards
vade Oct 24, 2024
421ab51
Add heuristic to calculate the largest track size and use as our comp…
vade Oct 24, 2024
72a3e79
Update gap calculations
vade Oct 24, 2024
17d4b62
Add defensive checks to our missing asset to ensure we only add it if…
vade Oct 24, 2024
fffe9ab
This is admittedly lame.
vade Oct 24, 2024
4b6ef7e
update our AVComposition building to ensure we disregard zero time ed…
vade Oct 24, 2024
680b20c
Update our zoom scale
vade Oct 24, 2024
278a4aa
Beta
vade Oct 24, 2024
14670b9
Update README.md
vade Oct 24, 2024
ff78d34
Fix bug in URL percent encoding handling with paths / file lookup cau…
vade Oct 31, 2024
00a0cd8
Allow default hit testing
vade Oct 31, 2024
d571523
Attempt to fix a timing ambiguity / interpretation issue in the Premi…
vade Oct 31, 2024
0cef719
Update our time scale logic to better reflect a semantic that OTIO ex…
vade Oct 31, 2024
0b3a73d
Add defensive checks for min frame durations to be valid and non zero…
vade Nov 7, 2024
0661178
Add a fix for mxf support.
vade Nov 7, 2024
26a8083
Add a fix to discover local files that may be referenced off of an ot…
vade Nov 7, 2024
dcb6c37
Scheme was missing for some reason?
vade Nov 7, 2024
fa38527
Beta 4
vade Nov 7, 2024
af8cf30
Update README.md
vade Nov 7, 2024
4e01d78
Update README.md
vade Nov 8, 2024
aa69e8c
First step to marker support - some test data borrowed from OTIO Core…
vade Jul 1, 2025
24cd6a5
Draft of marker view
vade Jul 1, 2025
d3b7c86
Wire basic marker view up - need to validate placement logic
vade Jul 2, 2025
02055bf
Fix marker position on main stack
vade Jul 2, 2025
7114ad7
Better marker drawing
vade Jul 2, 2025
240ae63
Xleanup
vade Jul 2, 2025
40946f1
more tweaks
vade Jul 2, 2025
8ceb8b8
Nicer marker drawing
vade Jul 2, 2025
b6bf13a
Cleanup
vade Jul 2, 2025
597c4c3
Fix alignment of TimelineRuler canvas to allow for "overdraw" fixing #25
vade Jul 2, 2025
8589d6d
Draft of marker view
vade Jul 1, 2025
e2e7636
Wire basic marker view up - need to validate placement logic
vade Jul 2, 2025
0b9ef6b
Fix marker position on main stack
vade Jul 2, 2025
fc3df51
Better marker drawing
vade Jul 2, 2025
cc9453c
Xleanup
vade Jul 2, 2025
2d36954
more tweaks
vade Jul 2, 2025
798aaf1
Nicer marker drawing
vade Jul 2, 2025
e9dafc6
Fix alignment of TimelineRuler canvas to allow for "overdraw" fixing #25
vade Jul 2, 2025
94472ff
Fix error
vade Jul 2, 2025
dd1c023
Merge branch 'feature/marker-support' of https://github.com/Synopsis/…
vade Jul 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ struct ContentView: View
self.fileURL = fileURL

guard let fileURL = fileURL else { return }




self.document.setupPlayerWithBaseDocumentURL(fileURL)
}

Expand Down Expand Up @@ -65,6 +63,7 @@ struct ContentView: View
self.controlsViewStack()
}
}

}
detail: {
ItemInspectorView(selectedItem: self.$selectedItem)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ struct ItemInspectorView: View
}
.font(.system(size: 10))
.listRowSeparator(.hidden)

}

func safeToJSON(item: Item) -> String
Expand Down
77 changes: 43 additions & 34 deletions OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,47 +13,42 @@ import SwiftUI
struct ItemView : View {

let item:OpenTimelineIO.Item
@State var backgroundColor:Color
@State var selected:Bool
let backgroundColor:Color
let selected:Bool

private var isGap: Bool {
item.isKind(of: Gap.self)
}

@Binding var secondsToPixels:Double

var body: some View
{

ZStack {

if let _ = item as? Gap
{
RoundedRectangle(cornerRadius: 3)
.fill(Color("GapTrackBaseColor")) // Fill the RoundedRectangle with color
.overlay(
RoundedRectangle(cornerRadius: 3)
.stroke(self.selected ? .white : .clear, lineWidth: 1) // Add stroke/outline
)
.frame(width: self.getSafeWidth() - 2)
}
else
{
ZStack
{
let fill:AnyShapeStyle = ( isGap ) ? AnyShapeStyle( Color("GapTrackBaseColor") ) : AnyShapeStyle(self.backgroundColor.gradient)
let textGapOpacity:Double = ( isGap ) ? 0.0 : 1.0

RoundedRectangle(cornerRadius: 3)
.fill(fill, style: FillStyle())
.overlay(
RoundedRectangle(cornerRadius: 3)
.fill(self.backgroundColor.gradient) // Fill the RoundedRectangle with color
.overlay(
RoundedRectangle(cornerRadius: 3)
.stroke(self.selected ? .white : .clear, lineWidth: 1) // Add stroke/outline
)
.strokeBorder(self.selected ? .orange : .clear, lineWidth: 1) // Add stroke/outline
.frame(width: self.getSafeWidth() - 2)

if self.getSafeWidth() > 40
{
Text(item.name)
.lineLimit(1)
.font(.system(size: 10))
.frame(width: self.getSafeWidth())
}
}
}
.frame(width: self.getSafeWidth())
// .offset(x:self.getSafePositionX() )//, y:geometry.size.height * 0.5 )
)
.frame(width: self.getSafeWidth() - 2)

Text(item.name)
.lineLimit(1)
.font(.system(size: 10))
.frame(width: self.getSafeWidth())
.opacity(self.getSafeWidth() > 40 ? textGapOpacity : 0.0)
}
.frame(width: self.getSafeWidth(), alignment: .leading )
.overlay {
// TODO: Get item marker coordinate system working
// self.getMarkerView()
}
}

func getSafeRange() -> OpenTimelineIO.TimeRange
Expand Down Expand Up @@ -81,4 +76,18 @@ struct ItemView : View {
{
return self.getSafeRange().startTime.toSeconds() * self.secondsToPixels// + self.getSafeWidth()/2.0
}

@ViewBuilder func getMarkerView() -> some View
{
HStack(alignment: .top)
{
let markerRange = self.item.markers.startIndex ..< self.item.markers.endIndex
ForEach(markerRange, id:\.self) { markerIndex in
MarkerView(marker: self.item.markers[markerIndex],
secondsToPixels: self.$secondsToPixels)
}
}
.frame(width: self.getSafeWidth(), alignment: .leading )

}
}
58 changes: 58 additions & 0 deletions OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// MarkerView.swift
// OpenTimelineIO-Reader
//
// Created by Anton Marini on 7/1/25.
//

import OpenTimelineIO_AVFoundation
import OpenTimelineIO
import TimecodeKit
import SwiftUI

struct MarkerView : View {

let marker:OpenTimelineIO.Marker

@Binding var secondsToPixels:Double

var body: some View
{
self.colorForMarker()
.frame(width: self.getSafeWidth(), height: 7.0 )
.offset(x:self.getSafePositionX() )
}

func getSafeWidth() -> CGFloat
{
return max(self.marker.markedRange.duration.toSeconds() * self.secondsToPixels, 5.0)
}

func getSafePositionX() -> CGFloat
{
return self.marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + self.getSafeWidth()/2.0
}

func colorForMarker() -> Color
{
if let color:Marker.Color = Marker.Color(rawValue: marker.color)
{
switch color
{
case .pink : return Color.pink
case .red : return Color.red
case .orange : return Color.orange
case .yellow : return Color.yellow
case .green : return Color.green
case .cyan : return Color.cyan
case .blue : return Color.blue
case .purple : return Color.purple
case .magenta : return Color(red: 1.0, green: 0, blue: 1.0)
case .black : return Color.black
case .white : return Color.white
}
}

return Color.red
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import TimecodeKit
import SwiftUI
struct TimeRulerView: View
{

static let VerticalPadding:CGFloat = 100

let timeline: OpenTimelineIO.Timeline
@Binding var secondsToPixels: Double
@Binding var currentTime: OpenTimelineIO.RationalTime
Expand All @@ -19,22 +22,85 @@ struct TimeRulerView: View
{
Canvas { context, size in

context.translateBy(x: TrackView.trackHeaderWidth, y: 0)

let safeRange = getSafeRange()
let startSeconds = safeRange.startTime.toSeconds()
let endSeconds = safeRange.endTimeInclusive().toSeconds()

// Draw playhead
drawPlayhead(context: context, currentTime: currentTime, secondsToPixels: secondsToPixels, size: size)

// Draw ticks (including frame-level ticks)
drawTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size)

// Draw playhead
drawPlayhead(context: context, currentTime: currentTime, secondsToPixels: secondsToPixels, size: size)

drawMarkers(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size)

}
.frame(width: self.getSafeWidth())
.frame(width: self.getSafeWidth() + TrackView.trackHeaderWidth)
}

func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize)
{
let y = 20.0
if let tracks = self.timeline.tracks
{
let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex
for markerIndex in markerRange
{
let marker = tracks.markers[markerIndex]
let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels

let text = marker.name

if #available(macOS 14.0, *)
{
context.draw(
Text("\(Image(systemName: "arrowtriangle.down.fill"))")
.font(.system(size: 10))
.foregroundStyle(.red),
at: CGPoint(x: x + 0.5, y: y)
)


context.draw(
Text(text)
.font(.system(size: 10))
.foregroundStyle(.red),
at: CGPoint(x: x + 9 , y: y - 2.0),
anchor: UnitPoint(x: 0, y: 0.5)
)
}
else
{
context.draw(
Text("\(Image(systemName: "arrowtriangle.down.fill"))")
.font(.system(size: 10))
.foregroundColor(.red),
at: CGPoint(x: x + 0.5, y: y)
)

context.draw(
Text(text)
.font(.system(size: 10))
.foregroundColor(.red),
at: CGPoint(x: x + 9 , y: y - 2.0),
anchor: UnitPoint(x: 0, y: 0.5)
)
}

let tickHeight = 24.0

// Draw tick line
let tickRect = CGRect(x: x, y: size.height - tickHeight, width: 1, height: tickHeight)
context.fill(Path(tickRect), with: .color(.red))
}
}
}

func drawTicks(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize)
{
if self.secondsToPixels > 100
if self.secondsToPixels > 75
{
self.drawFrameTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size)
}
Expand Down Expand Up @@ -66,7 +132,7 @@ struct TimeRulerView: View
context.fill(Path(tickRect), with: .color(.white))

// Draw label if it's an hour or minute
if self.secondsToPixels > 50
if self.secondsToPixels > 75
{
context.draw(Text(label).font(.system(size: 10)), at: CGPoint(x: positionX + 2, y: size.height - tickHeight - 10))
}
Expand All @@ -93,9 +159,10 @@ struct TimeRulerView: View
let tickRect = CGRect(x: positionX, y: size.height - tickHeight, width: 1, height: tickHeight)
context.fill(Path(tickRect), with: .color(.white))

if self.secondsToPixels > 400
if self.secondsToPixels > 500
{
context.draw(Text(String(frameNum)).font(.system(size: 10)), at: CGPoint(x: positionX, y: size.height - tickHeight - 5))
context.draw(Text(String(frameNum)).font(.system(size: 10)),
at: CGPoint(x: positionX, y: size.height - tickHeight - 5))
}
}
}
Expand All @@ -114,7 +181,7 @@ struct TimeRulerView: View
}

let playheadPositionX = currentTime.toSeconds() * secondsToPixels
let playheadRect = CGRect(x: playheadPositionX, y: 20, width: 1, height: size.height-20)
let playheadRect = CGRect(x: playheadPositionX, y: 22, width: 1, height: size.height - 22)
// context.fill(Path(playheadRect), with: .color(.orange))

if #available(macOS 14.0, *)
Expand All @@ -127,7 +194,7 @@ struct TimeRulerView: View

context.draw(
Text(currentTimeLabel)
.font(.system(size: 10))
.font(.system(size: 10, weight: .bold))
.foregroundStyle(.orange),
at: CGPoint(x: playheadPositionX, y: 5))
}
Expand All @@ -141,7 +208,7 @@ struct TimeRulerView: View

context.draw(
Text(currentTimeLabel)
.font(.system(size: 10))
.font(.system(size: 10, weight: .bold))
.foregroundColor(.orange),
at: CGPoint(x: playheadPositionX, y: 5))
}
Expand Down
Loading