authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-07-12 17:28:34-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-07-12 17:29:19-07:00
log3fb1a65292351a32ab3a14059337f9bc8f4d6f96
treea6b0d37be30e59c0669ae6a28c9669b8015ae0d4
parenteb97c459390bdb39eb6ecf071fd0932a8386edac
signaturebadge-check Signed by SSH key SHA256:xbd+BjjhyBfwk7GVoURf9Yx0gzDerHbvYv7SddNWmAs

add clover pitch and bugs for sequencer


38 files changed, 4470 insertions(+), 11832 deletions(-)

pitch/Sources/Pitch/AudioEngine.swift+41
......@@ -35,6 +35,9 @@ final class AudioEngine: ObservableObject, @unchecked Sendable {
3535 @Published var isRunning = false
3636 @Published var permission: MicPermission = .unknown
3737 @Published var statusMessage: String?
38 /// Rolling key estimate (updated on the main thread as notes land). Low
39 /// frequency, so publishing it doesn't churn the SwiftUI tree per frame.
40 @Published var keyEstimate: KeyEstimate?
3841
3942 private let engine = AVAudioEngine()
4043 // Sensitive settings: low RMS gate so quiet singing registers, and a
......@@ -56,6 +59,11 @@ final class AudioEngine: ObservableObject, @unchecked Sendable {
5659 // Small median window to reject single-frame octave/spike errors.
5760 private var recentMidi = [Double]()
5861
62 // Note segmentation ("intentional note" layer) + its guard lock. Fed on the
63 // audio thread; read by the graph on the display thread.
64 private let segmenter = NoteSegmenter()
65 private let notesLock = NSLock()
66
5967 // History shared with the UI thread.
6068 private let historyLock = NSLock()
6169 private var history = [PitchSample]()
......@@ -179,6 +187,8 @@ final class AudioEngine: ObservableObject, @unchecked Sendable {
179187 recentMidi.removeAll(keepingCapacity: true)
180188 framesProcessed = 0
181189 haveStart = false
190 notesLock.lock(); segmenter.reset(); notesLock.unlock()
191 DispatchQueue.main.async { self.keyEstimate = nil }
182192 }
183193
184194 // MARK: - Audio-thread processing
......@@ -210,6 +220,7 @@ final class AudioEngine: ObservableObject, @unchecked Sendable {
210220
211221 let sr = detector.sampleRate
212222 var newSamples = [PitchSample]()
223 var noteCommitted = false
213224
214225 while accumulator.count >= windowSize {
215226 var result: YINDetector.Result?
......@@ -225,8 +236,14 @@ final class AudioEngine: ObservableObject, @unchecked Sendable {
225236 newSamples.append(PitchSample(time: windowStartTime, midi: midi, clarity: r.clarity))
226237 setLive(LivePitch(frequency: freq, midi: midi, clarity: r.clarity, level: r.level),
227238 at: windowStartTime)
239 notesLock.lock()
240 if segmenter.feed(time: windowStartTime, midi: midi) { noteCommitted = true }
241 notesLock.unlock()
228242 } else {
229243 recentMidi.removeAll(keepingCapacity: true)
244 notesLock.lock()
245 if segmenter.feedSilence(now: windowStartTime) { noteCommitted = true }
246 notesLock.unlock()
230247 }
231248
232249 accumulator.removeFirst(hop)
......@@ -234,6 +251,30 @@ final class AudioEngine: ObservableObject, @unchecked Sendable {
234251 }
235252
236253 if !newSamples.isEmpty { appendHistory(newSamples) }
254 if noteCommitted { updateKey() }
255 }
256
257 /// Recompute the key from committed notes (duration-weighted). Called only
258 /// when a note lands, so it's cheap; the result is published on the main
259 /// thread for the top-bar readout.
260 private func updateKey() {
261 notesLock.lock()
262 var weights = [Double](repeating: 0, count: 12)
263 for n in segmenter.committed {
264 weights[(((n.midi % 12) + 12) % 12)] += n.duration
265 }
266 notesLock.unlock()
267 let est = Music.estimateKey(weights: weights)
268 DispatchQueue.main.async { self.keyEstimate = est }
269 }
270
271 /// Snapshot of committed notes ending at/after `since`, plus the note being
272 /// sung right now (if any). Read by the graph each frame.
273 func noteSnapshot(since: Double) -> (committed: [NoteEvent], pending: NoteEvent?) {
274 notesLock.lock()
275 defer { notesLock.unlock() }
276 let committed = segmenter.committed.filter { $0.offset >= since }
277 return (committed, segmenter.pending)
237278 }
238279
239280 /// Median-of-3 over consecutive detections — removes lone octave/spike
pitch/Sources/Pitch/Music.swift+58
......@@ -73,6 +73,64 @@ enum Music {
7373 }
7474}
7575
76// MARK: - Key estimation (Krumhansl–Schmuckler)
77
78struct KeyEstimate {
79 let tonic: Int // pitch class 0–11 (concert)
80 let isMajor: Bool
81 let confidence: Double // 0…1, gap between the best and next-best fit
82}
83
84extension Music {
85 // Krumhansl–Kessler tonal-hierarchy profiles: how strongly each scale
86 // degree "belongs" in a major / minor key. Index 0 == the tonic.
87 static let majorProfile: [Double] =
88 [6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88]
89 static let minorProfile: [Double] =
90 [6.33, 2.68, 3.52, 5.38, 2.60, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17]
91
92 /// Estimate key from a 12-bin, duration-weighted pitch-class histogram.
93 /// Correlates the histogram against all 24 key profiles and returns the best
94 /// fit. Returns nil until there's enough distinct pitch material to be useful.
95 static func estimateKey(weights: [Double]) -> KeyEstimate? {
96 guard weights.count == 12 else { return nil }
97 guard weights.filter({ $0 > 0 }).count >= 3 else { return nil }
98
99 func correlate(_ x: [Double], _ p: [Double]) -> Double {
100 let mx = x.reduce(0, +) / 12, mp = p.reduce(0, +) / 12
101 var num = 0.0, dx = 0.0, dp = 0.0
102 for i in 0..<12 {
103 let a = x[i] - mx, b = p[i] - mp
104 num += a * b; dx += a * a; dp += b * b
105 }
106 let den = (dx * dp).squareRoot()
107 return den == 0 ? 0 : num / den
108 }
109
110 var best = (r: -2.0, tonic: 0, major: true)
111 var second = -2.0
112 for tonic in 0..<12 {
113 var rot = [Double](repeating: 0, count: 12)
114 for i in 0..<12 { rot[i] = weights[(i + tonic) % 12] }
115 for (r, isMaj) in [(correlate(rot, majorProfile), true),
116 (correlate(rot, minorProfile), false)] {
117 if r > best.r { second = best.r; best = (r, tonic, isMaj) }
118 else if r > second { second = r }
119 }
120 }
121 guard best.r > 0 else { return nil }
122 return KeyEstimate(tonic: best.tonic, isMajor: best.major,
123 confidence: max(0, min(1, best.r - max(0, second))))
124 }
125
126 /// Display name for a key, honoring the current naming + transpose, e.g.
127 /// "G major".
128 static func keyName(_ key: KeyEstimate, naming: NoteNaming, transpose: Int) -> String {
129 let pc = (((key.tonic + transpose) % 12) + 12) % 12
130 return "\(naming.names[pc]) \(key.isMajor ? "major" : "minor")"
131 }
132}
133
76134// MARK: - Vocal / instrument ranges
77135
78136struct PitchRange: Identifiable, Hashable {
pitch/Sources/Pitch/NoteSegmenter.swift created+120
......@@ -0,0 +1,120 @@
1import Foundation
2
3/// One committed "intentional" note — a stable pitch you actually meant to sing,
4/// with a start, an end, and how sharp/flat you held it.
5struct NoteEvent: Identifiable {
6 let id: Int
7 let midi: Int // integer MIDI (concert pitch, before transpose)
8 let onset: Double // seconds on the CACurrentMediaTime clock
9 var offset: Double // seconds
10 let meanCents: Double // signed average deviation from the integer, in cents
11 var duration: Double { offset - onset }
12}
13
14/// Turns a stream of fractional-MIDI pitch samples into discrete note events —
15/// the piano-roll layer that sits above the raw pitch contour.
16///
17/// This is a lightweight, real-time analogue of pYIN's note HMM:
18/// • a tolerance band around the current note absorbs vibrato and small drift,
19/// so a wobbling held note reads as ONE note instead of flickering;
20/// • when the pitch leaves that band the note is closed and a new one opens;
21/// • notes shorter than `minDuration` are discarded, so scoops and passing
22/// tones on the way to a target don't become spurious notes;
23/// • a silence gap longer than `gapToEnd` closes the held note.
24final class NoteSegmenter {
25 // Tunables. Cents are hundredths of a semitone; a semitone is 100 cents, so
26 // a note "owns" ±centsTolerance around its center — the overlap past the
27 // 50-cent midpoint is the hysteresis that keeps vibrato from splitting notes.
28 private let centsTolerance = 62.0
29 private let minDuration = 0.10 // seconds; below this a region is dropped
30 private let gapToEnd = 0.13 // seconds of silence that ends a held note
31 private let retain = 30.0 // seconds of committed notes kept
32
33 private(set) var committed: [NoteEvent] = []
34
35 // In-progress note.
36 private var pendingMidi: Int?
37 private var pendingOnset = 0.0
38 private var pendingCentsSum = 0.0
39 private var pendingCount = 0
40 private var lastVoiced = 0.0
41 private var nextID = 0
42
43 /// Feed one voiced sample. Returns true iff a note was committed (ended).
44 @discardableResult
45 func feed(time: Double, midi: Double) -> Bool {
46 var didCommit = false
47
48 // A silence gap since the previous voiced sample closes the held note.
49 if pendingMidi != nil, time - lastVoiced > gapToEnd {
50 didCommit = closePending(offset: lastVoiced)
51 }
52
53 if let cur = pendingMidi {
54 let distCents = abs(midi - Double(cur)) * 100
55 if distCents <= centsTolerance {
56 // Still the same note — absorb the sample (this is what swallows
57 // vibrato even as the nearest semitone flips back and forth).
58 pendingCentsSum += (midi - Double(cur)) * 100
59 pendingCount += 1
60 lastVoiced = time
61 } else {
62 // Pitch has moved off the note: close it, open a new one.
63 didCommit = closePending(offset: lastVoiced) || didCommit
64 openPending(time: time, midi: midi)
65 }
66 } else {
67 openPending(time: time, midi: midi)
68 }
69 return didCommit
70 }
71
72 /// Feed a detector "silence" tick so a held note eventually closes even if no
73 /// further voiced sample arrives. (Gaps are also caught lazily in `feed`.)
74 @discardableResult
75 func feedSilence(now: Double) -> Bool {
76 guard pendingMidi != nil, now - lastVoiced > gapToEnd else { return false }
77 return closePending(offset: lastVoiced)
78 }
79
80 /// The note currently being sung, as a provisional event for live rendering.
81 var pending: NoteEvent? {
82 guard let m = pendingMidi, pendingCount > 0 else { return nil }
83 // Only surface it once it's plausibly a note, not a passing scoop.
84 guard lastVoiced - pendingOnset >= minDuration * 0.5 else { return nil }
85 return NoteEvent(id: -1, midi: m, onset: pendingOnset, offset: lastVoiced,
86 meanCents: pendingCentsSum / Double(pendingCount))
87 }
88
89 func reset() {
90 committed.removeAll(keepingCapacity: true)
91 pendingMidi = nil
92 pendingCount = 0
93 }
94
95 // MARK: - Private
96
97 private func openPending(time: Double, midi: Double) {
98 let m = Int(midi.rounded())
99 pendingMidi = m
100 pendingOnset = time
101 pendingCentsSum = (midi - Double(m)) * 100
102 pendingCount = 1
103 lastVoiced = time
104 }
105
106 @discardableResult
107 private func closePending(offset: Double) -> Bool {
108 guard let m = pendingMidi else { return false }
109 pendingMidi = nil
110 guard offset - pendingOnset >= minDuration, pendingCount > 0 else { return false }
111 committed.append(NoteEvent(id: nextID, midi: m, onset: pendingOnset,
112 offset: offset, meanCents: pendingCentsSum / Double(pendingCount)))
113 nextID += 1
114 let cutoff = offset - retain
115 if let first = committed.first, first.offset < cutoff {
116 committed.removeAll { $0.offset < cutoff }
117 }
118 return true
119 }
120}
pitch/Sources/Pitch/PitchGraphView.swift+27
......@@ -78,6 +78,7 @@ private struct TraceCanvas: View {
7878 let visible = settings.visibleSeconds
7979 let live = engine.currentLive()
8080
81 drawNotes(ctx: &ctx, layout: layout, now: now, visible: visible)
8182 drawBand(ctx: &ctx, size: size, layout: layout, live: live)
8283 drawTrace(ctx: &ctx, layout: layout, now: now, visible: visible)
8384 drawPill(ctx: &ctx, size: size, layout: layout, live: live)
......@@ -91,6 +92,32 @@ private struct TraceCanvas: View {
9192 private static let clockOffset: Double =
9293 CACurrentMediaTime() - Date().timeIntervalSinceReferenceDate
9394
95 // Piano-roll bars for the segmented "intentional" notes, drawn behind the
96 // live trace. Committed notes are solid; the note being sung right now is
97 // brighter and grows at the leading edge.
98 private func drawNotes(ctx: inout GraphicsContext, layout: PitchLayout,
99 now: Double, visible: Double) {
100 let (committed, pending) = engine.noteSnapshot(since: now - visible - 1)
101 // Bars are taller than the trace (4.2 px) so the note reads as a distinct
102 // block with the pitch line threading through it, not a sliver the trace
103 // hides. A crisp outline defines each note's edges.
104 let h = min(layout.rowHeight * 0.85, 26)
105
106 func bar(_ n: NoteEvent, fill: Color) {
107 let x0 = max(0, layout.x(n.onset, now: now, visible: visible))
108 let x1 = min(layout.plotWidth, layout.x(n.offset, now: now, visible: visible))
109 guard x1 > x0 else { return }
110 let yy = layout.y(Double(n.midi))
111 let rect = CGRect(x: x0, y: yy - h / 2, width: x1 - x0, height: h)
112 let path = Path(roundedRect: rect, cornerRadius: min(5, h / 2))
113 ctx.fill(path, with: .color(fill))
114 ctx.stroke(path, with: .color(palette.noteBarEdge), lineWidth: 1)
115 }
116
117 for n in committed { bar(n, fill: palette.noteBar) }
118 if let p = pending { bar(p, fill: palette.notePending) }
119 }
120
94121 // Thin solid coral line marking the nearest note — a couple of pixels
95122 // thicker than a staff line.
96123 private func drawBand(ctx: inout GraphicsContext, size: CGSize,
pitch/Sources/Pitch/Theme.swift+11-2
......@@ -15,6 +15,9 @@ struct Palette {
1515 var pillText: Color
1616 var trace: Color
1717 var dot: Color
18 var noteBar: Color // committed "intentional" note
19 var notePending: Color // the note being sung right now
20 var noteBarEdge: Color // crisp outline so bars read under the trace
1821
1922 static func make(_ scheme: ColorScheme) -> Palette {
2023 scheme == .dark ? .dark : .light
......@@ -31,7 +34,10 @@ struct Palette {
3134 pill: Color(red: 0.93, green: 0.49, blue: 0.45),
3235 pillText: Color.white,
3336 trace: Color(red: 0.20, green: 0.24, blue: 0.31),
34 dot: Color(red: 0.20, green: 0.24, blue: 0.31))
37 dot: Color(red: 0.20, green: 0.24, blue: 0.31),
38 noteBar: Color(red: 0.36, green: 0.52, blue: 0.90).opacity(0.28),
39 notePending: Color(red: 0.36, green: 0.52, blue: 0.90).opacity(0.44),
40 noteBarEdge: Color(red: 0.28, green: 0.44, blue: 0.85).opacity(0.65))
3541
3642 static let dark = Palette(
3743 background: Color(red: 0.09, green: 0.10, blue: 0.12),
......@@ -44,7 +50,10 @@ struct Palette {
4450 pill: Color(red: 0.90, green: 0.47, blue: 0.44),
4551 pillText: Color.white,
4652 trace: Color(red: 0.93, green: 0.95, blue: 0.99),
47 dot: Color(red: 0.93, green: 0.95, blue: 0.99))
53 dot: Color(red: 0.93, green: 0.95, blue: 0.99),
54 noteBar: Color(red: 0.55, green: 0.68, blue: 0.99).opacity(0.26),
55 notePending: Color(red: 0.55, green: 0.68, blue: 0.99).opacity(0.44),
56 noteBarEdge: Color(red: 0.62, green: 0.74, blue: 1.0).opacity(0.7))
4857}
4958
5059/// Shared plot geometry so the static grid layer and the animated trace layer
pitch/Sources/Pitch/TopBar.swift+39
......@@ -52,6 +52,7 @@ struct TopBar: View {
5252
5353 Spacer(minLength: 8)
5454
55 KeyPill(engine: engine, settings: settings)
5556 StatusPill(engine: engine)
5657 }
5758 .padding(.horizontal, 16)
......@@ -92,6 +93,44 @@ private struct Dropdown<Content: View>: View {
9293 }
9394}
9495
96/// Live key estimate (Krumhansl–Schmuckler) shown in the top bar. Dims when the
97/// fit is weak so a shaky guess reads as tentative.
98private struct KeyPill: View {
99 @ObservedObject var engine: AudioEngine
100 @ObservedObject var settings: Settings
101
102 var body: some View {
103 HStack(spacing: 6) {
104 Image(systemName: "music.note")
105 .font(.system(size: 11, weight: .semibold))
106 .foregroundColor(.secondary)
107 Text(text)
108 .font(.system(size: 11, weight: .medium))
109 .foregroundColor(.secondary)
110 .lineLimit(1)
111 }
112 .opacity(opacity)
113 .padding(.horizontal, 10)
114 .padding(.vertical, 5)
115 .background(
116 RoundedRectangle(cornerRadius: 8)
117 .fill(Color(nsColor: .controlBackgroundColor))
118 .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color(nsColor: .separatorColor), lineWidth: 1))
119 )
120 .help("Estimated key (Krumhansl–Schmuckler), from the notes you've sung")
121 }
122
123 private var text: String {
124 guard let k = engine.keyEstimate else { return "Key —" }
125 return "Key " + Music.keyName(k, naming: settings.naming, transpose: settings.transpose)
126 }
127
128 private var opacity: Double {
129 guard let k = engine.keyEstimate else { return 0.55 }
130 return 0.6 + 0.4 * min(1, k.confidence / 0.1)
131 }
132}
133
95134private struct StatusPill: View {
96135 @ObservedObject var engine: AudioEngine
97136
recorder/engine/Sources/recorder/CaptureEngine.swift+21-3
......@@ -276,15 +276,21 @@ final class CaptureEngine {
276276 fallbackDir: safeRoot)
277277 }
278278
279 private func makeAudioWriter(name: String, kind: String) throws -> StreamWriter {
279 private func makeAudioWriter(name: String, kind: String, channels: Int = 2) throws -> StreamWriter {
280280 // AAC in .m4a: ~256 kbps stereo, transparent for voice/desktop and tiny next
281281 // to the uncompressed PCM we used to write. Audio is the irreplaceable
282282 // stream and costs ~115 MB/hour, so it records to the safe (internal) disk
283283 // rather than the removable scratch drive.
284 //
285 // `channels` must match the source: system audio is genuinely stereo, but a
286 // mono mic forced into a 2-channel file lands entirely in channel 0 (left)
287 // and leaves the right dead silent — the file then plays only in the left
288 // ear on headphones. Writing a true mono file lets AVFoundation upmix it to
289 // both channels on playback.
284290 let settings: [String: Any] = [
285291 AVFormatIDKey: kAudioFormatMPEG4AAC,
286292 AVSampleRateKey: 48_000,
287 AVNumberOfChannelsKey: 2,
293 AVNumberOfChannelsKey: channels,
288294 AVEncoderBitRateKey: 256_000,
289295 ]
290296 return try StreamWriter(
......@@ -293,6 +299,14 @@ final class CaptureEngine {
293299 fallbackDir: safeRoot)
294300 }
295301
302 /// Channel count of an audio capture device's active format (1 for a typical
303 /// built-in or USB mic), clamped to at least 1. Used to size the mic writer.
304 private static func channelCount(of device: AVCaptureDevice) -> Int {
305 guard let asbd = CMAudioFormatDescriptionGetStreamBasicDescription(
306 device.activeFormat.formatDescription) else { return 1 }
307 return max(1, Int(asbd.pointee.mChannelsPerFrame))
308 }
309
296310 // MARK: AVCapture (mic + camera)
297311
298312 private func startAVCapture(audioUID: String? = nil, videoUID: String? = nil) throws {
......@@ -307,7 +321,11 @@ final class CaptureEngine {
307321 guard session.canAddInput(input) else { throw RecorderError("cannot add mic input") }
308322 session.addInput(input)
309323
310 let writer = try makeAudioWriter(name: "mic", kind: "mic")
324 // Match the file's channel count to the device so a mono mic records as a
325 // true mono file (which plays in both ears) rather than a stereo file with
326 // a dead right channel. Falls back to mono if the format can't be read.
327 let micChannels = Self.channelCount(of: device)
328 let writer = try makeAudioWriter(name: "mic", kind: "mic", channels: micChannels)
311329 writer.deviceUID = device.uniqueID
312330 sinks.append(writer)
313331
sequencer/CLAUDE.md+37-8
......@@ -5,11 +5,14 @@ Sequencer ("Clover Sequencer") is a native macOS app, not a video editor. It hel
55## Build & run
66
77```sh
8swift build # compile
9./run.sh # build, kill running instance, copy binary into Sequencer.app, relaunch
8swift build # compile (debug — for the CLI harnesses below)
9./run.sh # build RELEASE, kill running instance, copy into Sequencer.app, relaunch
10./run.sh --debug # same but ship the debug build (crash-chasing only)
1011```
1112
12`run.sh` is the standard dev loop — it copies `.build/debug/Sequencer` into `Sequencer.app/Contents/MacOS/Sequencer` and reopens the app bundle (needed for a proper app identity/menu bar, not just a bare executable).
13`run.sh` is the standard dev loop — it copies the built binary into `Sequencer.app/Contents/MacOS/Sequencer` and reopens the app bundle (needed for a proper app identity/menu bar, not just a bare executable). **It ships the `-c release` build by default**: timeline drawing is ~4× slower under `-Onone`, and shipping debug binaries is how the app spent months feeling slower than it was. Judge any perf perception against release.
14
15Launching the binary directly with `SEQ_DRAWPROF=1` in the environment (stderr to a file — NSLog is invisible under `open`) prints one `[drawprof]` line per second with draws/sec and mean/max ms per `TimelineView.draw` — the GUI-side counterpart to `--perftest`.
1316
1417There is no test target in Package.swift. Verification instead happens through two CLI-flag-driven harnesses baked into `main.swift`:
1518
......@@ -17,11 +20,27 @@ There is no test target in Package.swift. Verification instead happens through t
1720swift run Sequencer --selftest <mediafile> # headless pipeline check (see Selftest.swift)
1821swift run Sequencer --uitest # offscreen TimelineView harness (see UITest.swift)
1922swift run Sequencer --perftest <file.sq> # offscreen draw-timing harness (see PerfTest.swift)
23swift run Sequencer --cachetest <file.sq> [capGB] # cache-budget invariant check (see Cachetest.swift)
2024```
2125
2226- `--selftest` exercises the real media pipeline against a file you pass in: ffprobe, filmstrip/waveform generation, chunk-proxy building, AVFoundation playability of the built proxy, and prints sample Fusion Lua output. Useful when touching `MediaPipeline.swift` or `ChunkedProxy.swift`.
2327- `--uitest` hosts the real `TimelineView` in an offscreen window and drives it with synthetic `NSEvent`s (move, trim, slip, stretch, box select, split, links, storyboard split, comp parsing, fades, plus file-format migration/round-trip — ~100 assertions, PASS/FAIL printer). Useful when touching timeline gesture code.
24- `--perftest` loads a real `.sq` into an offscreen `TimelineView` and times `draw(_:)` while simulating a horizontal pan, at several zoom levels, in both full-detail and simplified (mid-scroll) modes, printing ms/frame and a per-section breakdown (`DrawProf`). Use it when touching timeline **drawing** — it's how the per-clip draw cost was found. The lesson baked into `TimelineView`: never build/tint an `NSImage(systemSymbolName:)` per clip/element per frame (SF-symbol tinting does a `lockFocus` composite) — bake once via `bakedSymbol(...)`. Clip labels and filmstrips are also gated to at-rest / wide-enough via the `isScrolling` simplified path.
28- `--perftest` loads a real `.sq` into an offscreen `TimelineView` and times `draw(_:)` at several zoom levels, printing **median and p90** ms/frame (medians resist the I/O spikes async thumbnail loads inject) plus a per-section `DrawProf` breakdown and tile blit/render counts. Each zoom is measured **direct vs tiled, in the same process** (thermal drift between runs otherwise swamps real deltas — this machine varies 5× with battery/heat), under two pan patterns: `scroll` (8 px/frame, a real gesture) and `jump` (half-project teleports, cache-hostile). Use it when touching timeline **drawing**. Lessons baked into `TimelineView`: never build/tint an `NSImage(systemSymbolName:)` per clip per frame — bake once via `bakedSymbol(...)`; never draw geometry millions of points wide (clamp to the cull window; CG "clips" it but pays anyway); and detail is a function of **on-screen clip width** (`lodMinWidth`), never of whether the user is scrolling.
29- `--cachetest` loads a real `.sq` (headless), jumps the playhead across the timeline, and continuously asserts the cache-budget invariant (`ledger ≤ cap`, disk ≤ cap + slack) while demand builds and evictions run — plus coverage of the chunk under the playhead at each stop. Use it when touching the budget/eviction logic in `MediaPipeline.swift` or `ChunkedProxy.swift`. Run it against a project whose full proxy set exceeds the cap (pass a small `capGB`) — that's the regime it exists to test. Don't run it while the GUI app is open (two processes would fight over one cache).
30
31Cache/proxy debugging levers: `SEQ_BUILDLOG=1` logs every chunk-build START and admission failure (`[cache] start/admission blocked` — success completions are otherwise silent, so this is how you chase "why isn't chunk X building"). The viewer logs every "Loading Media…" spinner occurrence as a `[miss]` line with a cause diagnosis from `ChunkManager.missDiagnosis` (chunk missing/building/queued-where, composition-lag, player-late, budget-starved) and, on recovery, its duration. **All `[miss]` lines and important `[cache]` events (rescues, build/stitch failures, admission blocks, orphan reaping) also append to `~/Library/Logs/Sequencer.log`** (`SeqLog`, 5 MB rotation) — no Console attach or special launch needed; a user-reported spinner sighting is answerable from that file. `sequencer --layertest <cacheKey:origPath> […]` opens bare AVPlayerLayer tiles playing the real stitched composition(s) (`SEQ_LAYERTEST_T=<sec>` sets the seek) — the isolation tool that cracked the black-viewer bug.
32
33Two hard-won playback failure modes (2026-07-11): (1) **an `AVAssetTrack` does not retain its `AVURLAsset`** — stitching a track whose asset has been deallocated fails with -11800/-12780, and under `try?` that silently left a black GAP in the composition for a chunk that was healthy on disk (`LoadedPart.asset` exists precisely to prevent this; `comp insert FAILED` in the log means it's back). (2) **Orphaned ffmpeg encoders** (app killed mid-build — run.sh pkills on every rebuild) hold VideoToolbox decode sessions from a machine-wide pool; a dozen accumulated orphans make every AVPlayer render black with zero errors anywhere (items ready, seeks land, `isReadyForDisplay` true). Defenses: `MediaPipeline.reapOrphans` kills stale ffmpegs referencing our cache at launch, `terminateChildren` runs on quit and SIGTERM/SIGINT, and run.sh sweeps strays.
34
35Bundle id: `net.paperclover.Sequencer` (defaults domain too; renamed from com.clover.Sequencer 2026-07-12). Document UTI: `net.paperclover.sequencer.project` — keep `ProjectDocumentController.projectType` and build.sh's Info.plist in sync.
36
37`ProjectModel.laneRefs` (and `hasStoryboard`) scan every clip — O(clips). Never call them per-clip in a draw or per-frame path: `laneRect→laneRefs` per drawn clip was quadratic and pegged a core at 98% on big projects. `TimelineView` uses its `laneRows` cache (invalidated in `redraw()`/`ctx.didSet`); route new hot paths through that.
38
39Two hard-won main-thread rules (2026-07-12): (1) **`URL(fileURLWithPath:)` without `isDirectory:` stats the path** — `MediaItem.url`/`displayName` did this per clip label per timeline draw against NAS paths, freezing the UI whenever the volume was cold (292/337 draw samples in `stat()`). Always pass `isDirectory:` for known-file paths; `displayName` uses `(path as NSString).lastPathComponent` (no I/O). (2) **Nothing between `thread_suspend` and `thread_resume` may allocate or lock** — the hang sampler's `Array.append` needed the malloc lock the suspended main thread held → permanent app freeze (the "unusably laggy on startup" report). `sampleMainStack` uses preallocated buffers; keep it allocation-free. Related: the SIGTERM/SIGINT DispatchSources live on a background queue, not main — a wedged main thread must not make the app unkillable.
40
41Microhangs/stutter: `HangMonitor` (a watchdog thread pinging the main queue every 20 ms) logs every main-thread stall >100 ms as a `[hang]` line in `~/Library/Logs/Sequencer.log` — duration, whether playback was running (`during playback (rate …)`), and a **symbolicated stack of the main thread sampled mid-stall** (mach `thread_suspend` + arm64 frame-pointer walk + `swift_demangle`), so the culprit is named without Instruments. `SEQ_NOHANGWATCH=1` disables it; `SEQ_HANGTEST=1` injects a deliberate 0.4 s spin ~2 s after launch to verify the pipeline end-to-end. User-reported stutter during long sessions is answerable from that file.
42
43RAM: the `maxRAMGB` default (Settings → Global, default 2) budgets `FrameCache` — decoded exact stand-in frames for cut boundaries and not-yet-presentable moments (warmed ahead by `VideoTrackPlayer.warmBoundaryFrames`, on demand by the viewer; `SEQ_NOWARM=1` disables decodes) — and scales the video players' forward buffer (`ramGB/2` seconds, capped at 8).
2544
2645Requires `ffmpeg`/`ffprobe` on PATH for anything touching media (probing, filmstrips, proxies).
2746
......@@ -48,7 +67,7 @@ Non-undoable session/UI state (track hide/focus, pane heights, laneScale, snappi
4867
4968### Per-document architecture (`DocumentContext.swift`, `Document.swift`, `WindowController.swift`)
5069
51Each open project is a `ProjectDocument: NSDocument` owning a `DocumentContext` — the per-document service bag. Everything per-project is an instance on it: `ctx.store`, `ctx.playback`, `ctx.players`, `ctx.chunks`, `ctx.comps`, `ctx.boards`, `ctx.session`. Each service holds an `unowned var ctx` back-reference, so service-to-service calls go through `ctx.*`. Views reach their state through a stored `var ctx` injected at construction (`SequencerWindowController` sets `timeline/viewer/transport`'s `ctx`; the grid injects its cells; the storyboard editor is re-targeted per `open(clipId:ctx:)`). **Do not reach for a global `.shared` for per-project state** — only `MediaPipeline` (content-addressed media cache) and `Theme` are genuinely global. `DocumentContext.current` resolves the front document's context for app-level actions (Settings/Export/cache eviction); `DocumentContext.headless` backs the `--uitest`/`--selftest` harnesses.
70Each open project is a `ProjectDocument: NSDocument` owning a `DocumentContext` — the per-document service bag. Everything per-project is an instance on it: `ctx.store`, `ctx.playback`, `ctx.players`, `ctx.chunks`, `ctx.comps`, `ctx.boards`, `ctx.session`. Each service holds an `unowned var ctx` back-reference, so service-to-service calls go through `ctx.*`. **Because `ctx` is `unowned`, any service that lands an async callback (off-main chunk builds, the 60 Hz clock) must stop touching `ctx` once the document is closing** — `ProjectDocument.close()` calls `DocumentContext.shutdown()`, which flips `ChunkManager.stop()` / `PlaybackController.stop()` (both guard their ctx-touching entry points) *before* the context deallocs, so a build finishing after close can't trap on a dangling reference. Add the same guard to any new service that schedules deferred work. Views reach their state through a stored `var ctx` injected at construction (`SequencerWindowController` sets `timeline/viewer/transport`'s `ctx`; the grid injects its cells; the storyboard editor is re-targeted per `open(clipId:ctx:)`). **Do not reach for a global `.shared` for per-project state** — only `MediaPipeline` (content-addressed media cache) and `Theme` are genuinely global. `DocumentContext.current` resolves the front document's context for app-level actions (Settings/Export/cache eviction); `DocumentContext.headless` backs the `--uitest`/`--selftest` harnesses.
5271
5372`SequencerWindowController` (one per window) owns the split layout, previews pop-out, and every per-document menu action (`@objc func`s targeting the first responder). `AppDelegate` is now slim: it builds the menu bar and handles app-level actions only (New/Open route to `NSDocumentController`; Save/Save As/Close to `NSDocument`).
5473
......@@ -59,7 +78,7 @@ Each open project is a `ProjectDocument: NSDocument` owning a `DocumentContext`
5978This is the most complex subsystem — a three-stage pipeline. `ChunkManager` and `PlaybackController`/`PlayerManager` are per-document (`ctx.chunks`, `ctx.playback`, `ctx.players`); `MediaPipeline` is the one global (a shared content-addressed cache):
6079
61801. **`MediaPipeline`** (`MediaPipeline.swift`) — wraps ffprobe/ffmpeg. Probes media, generates filmstrips (thumbnail strips) and waveforms async, LRU content-addressed cache (default 50GB). Cache key = `SHA256(path|size|mtime)`, so moved/remounted files with identical content still hit cache.
622. **`ChunkManager`** (`ChunkedProxy.swift`) — demand-driven **30-second ProRes Proxy chunks** instead of whole-file transcodes. **Build order is the whole game** (`updateDemand` → `nextJob` → `pump`): every playback tick recomputes a best-first `demand` list from the playhead, playback direction, and visibility — for each video clip in a look-ahead window it scores the proxy chunks its source range needs (coverage before sharpening, visible/focused tracks before hidden, nearer the playhead in the playback direction before farther) so the frame you're about to see is always built first. Chunks outside the window fall to the whole-project background fill (`ensure`). Adaptive quality (4 resolution/fps tiers) steps down/up based on measured encode wall-time vs. realtime ratio and distinguishes network vs. encode bottlenecks; **imminent** coverage (uncovered, just ahead, visible) builds at that adaptive realtime quality so it lands in time, everything else at the full preview-quality target. Composes ready chunks + original-file fallback into a **per-media** `AVComposition` (`composition(for:)`), versioned so playback only swaps on a strict upgrade. Per-media (not per-track) is deliberate: only the clip under the playhead needs building, so the current frame is ready fast — a whole-track composition would have to assemble the entire timeline before it could show anything, which is far too slow for a big multicut project.
812. **`ChunkManager`** (`ChunkedProxy.swift`) — demand-driven **30-second ProRes Proxy chunks** instead of whole-file transcodes. **Build order is the whole game** (`updateDemand` → `nextJob` → `pump`): every playback tick recomputes a best-first `demand` list from the playhead, playback direction, and visibility — for each video clip in a look-ahead window it scores the proxy chunks its source range needs (coverage before sharpening, visible/focused tracks before hidden, nearer the playhead in the playback direction before farther) so the frame you're about to see is always built first. Chunks outside the window fall to the whole-project background fill (`ensure`). Adaptive quality (4 resolution/fps tiers) steps down/up based on measured encode wall-time vs. realtime ratio and distinguishes network vs. encode bottlenecks; **imminent** coverage (uncovered, just ahead, visible) builds at that adaptive realtime quality so it lands in time, everything else at the full preview-quality target. **Rescue slices**: when the playhead lands INSIDE an uncovered chunk, a short (~10 s) slice starting at the playhead builds first (`rNNNNNN.mov`, one quality rung lower) — read-bound NAS sources scale with encoded seconds, so this lands in a few seconds where quality drops can't help — then the full chunk builds right behind it and deletes the slice. Slice state (`MediaState.partial`) is session-only; stale slice files are purged by the launch reconcile. In queries: `isCovered` is true inside the slice's range, `builtWidths` reports a slice as width 1 (so the full build reads as a strict upgrade and the player adopts it), demand scoring still counts the chunk uncovered (so the full build stays queued). Trim/slip/ripple drags call `noteGestureExposure` mid-gesture so newly exposed source ranges build before mouse-up; committed edits warm the *edited edge* (`Store.noteEditLocus`). Composes ready chunks + rescue slices + original-file fallback into a **per-media** `AVComposition` (`composition(for:)`, chunk assets loaded concurrently), versioned so playback only swaps on a strict upgrade. Per-media (not per-track) is deliberate: only the clip under the playhead needs building, so the current frame is ready fast — a whole-track composition would have to assemble the entire timeline before it could show anything, which is far too slow for a big multicut project.
63823. **`PlaybackController`** + `PlayerManager` (`PlaybackController.swift`) — master clock anchored to `CACurrentMediaTime()`; all players chase this one authoritative time at 60Hz. Supports reverse and J/K/L shuttle speeds (±1/2/4/8/16/32/64). Each video track is a **`VideoTrackPlayer`: two `AVPlayer`s (front/back) for gapless cuts.** The front shows the clip under the playhead (`currentTime` == source time, chasing at `rate × clip.speed`); as a cut to a *different media* approaches (~1.5 s out) the back is prerolled to the next clip's first frame — loaded, decoded, parked, muted — and at the cut the roles flip. The viewer's cell holds two `AVPlayerLayer`s (one bound to each sub-player, stable) and just toggles which is visible, so the flip is a layer swap with no `replaceCurrentItem` black-frame gap. Same-media continuations don't buffer (the front keeps its item and seeks). `tick()` runs `sync()` (which does the flip) *before* posting `.playheadChanged`, so the viewer reflects the flip on the same frame. Standalone `.audio` clips get one `AVPlayer` per clip (looser sync tolerance since originals live on NAS); a clip's player is **prerolled ~1 s before its cut and kept running (silent — its fade gain is 0 until reached)**, so crossing an audio cut is a seamless volume handover rather than a cold start — see `audioLookahead`/`audioLinger` in `syncAudio`. Item swaps hold the fresh item silent (`rate = 0`) until the immediately-following `syncTime` seeks it to the live position, so a swap never blips wrong content from t=0. `.projectChanged` forces a hard resync only while **paused** — during playback the 60 Hz tick already tracks, so edits away from the playhead don't stutter. Seeks coalesce while one is in-flight.
6483
6584When working on playback bugs, the mental model is: `MediaPipeline` produces cached derived assets → `ChunkManager` decides what to build (playhead/visibility-ordered) and assembles per-media compositions → `PlaybackController` drives the players (a double-buffered pair per video track, one per audio clip) against those compositions on a shared clock. Two levers make cuts gapless: the **build order** gets the next clip's proxy ready ahead of time, and the **double buffer** prerolls it and flips without an item swap.
......@@ -71,11 +90,21 @@ When working on playback bugs, the mental model is: `MediaPipeline` produces cac
7190
7291### Storyboard (`Storyboard.swift`, `StoryboardEditor.swift`)
7392
74Each `Board` has two layers: a shape (vector) layer stored in the model (`BoardShape` — rect/oval/triangle/star/n-gon/text/image-ref) and a raster (drawing) layer stored as a PNG on disk keyed by board ID. `revision`/version counters invalidate the composite cache independent of model-mutation equality (raster edits don't go through `Store.mutate`). Panels are "start-only" in the model — see `normalizeStoryboards()` above.
93Each `Board` has two layers: a shape (vector) layer stored in the model (`BoardShape` — rect/oval/triangle/star/n-gon/text/image-ref) and a raster (drawing) layer stored as a PNG on disk keyed by board ID. `revision`/version counters invalidate the composite cache independent of model-mutation equality (raster pixels live in `BoardStore`, out of the value-type model). **Drawing IS undoable and shares ONE timeline with model edits:** `BoardStore.beginStroke`/`endStroke` capture the pre-stroke image and register it via `Store.recordRasterEdit`, so the `Store` undo stack holds both `.model(Snapshot)` and `.raster(boardId, image)` entries in the order they happened — ⌘Z/⌘⇧Z step through drawing and model edits together, from any window, and drawing redoes like everything else (undo/redo of a raster entry swaps the current drawing for the stored one via `BoardStore.rasterSnapshot`/`applyRaster`). Panels are "start-only" in the model — see `normalizeStoryboards()` above.
7594
7695### Views
7796
78- `TimelineView.swift` — largest file (~2700 lines); all editing gestures (move/trim/slip/stretch/box-select/split/blade) live here, each gesture wrapped in one `Store` undo step.
97- `TimelineView.swift` — largest file (~3500 lines); all editing gestures (move/trim/slip/stretch/box-select/split/blade) live here, each gesture wrapped in one `Store` undo step.
98
99 **Timeline rendering** is built around one rule: frame cost is proportional to what *changed*, not what's visible, and the look never degrades — a 4,740-clip project draws pixel-identically to a 10-clip one. Three mechanisms:
100 1. **Pixel-width LOD** (`drawClip`, `lodMinWidth`): a clip narrower than ~3 px physically can't show corners/labels/filmstrips, so it draws as flat rects (body + strip + audio center line + fade-handle slivers) — no bezier/clip-state chrome. This replaced the old `lightScroll` shed-detail-mid-scroll mode, which visibly dropped thumbnails yet saved almost nothing.
101 2. **Scene tile cache** (`blitLaneTiles`/`tileImage`): lane clip content rasterizes into per-(lane × 512 pt slice) `CGImage` tiles in *timeline space* (origin-independent), rendered by the same `drawLaneClips` code as the direct path — pixel-equivalent by construction. Pan/playback frames are blits + chrome (~15 ms at fit-all on a throttled M4; was ~500 ms). Pin-to-view-edge labels are an overlay (`drawPinnedLabels`), never baked. Origin/scrollY/lane heights are quantized to the device-pixel grid so blits never resample. Tiles are BGRA in the window's colorspace (an `NSBitmapImageRep` source costs a per-blit swizzle+conversion). Renders are budgeted (`tileRendersPerFrame`) with pixel-identical direct fallback + a scheduled fill pass; big origin jumps render zero tiles that frame. Edit gestures bypass tiles entirely. `SEQ_NOTILES=1` forces the direct path.
102 3. **Targeted invalidation**: `.projectChanged`/`.viewOptionsChanged` → scene regroup + tile flush; `.selectionChanged` → link-mate cache + tile flush only; `.mediaStatusChanged` flushes tiles **only when posted with `userInfo["scene"]`** (filmstrip/waveform/board raster landed — `MediaPipeline`/`BoardStore` posts carry it) — proxy-chunk churn during playback repaints without re-rendering. Keep that convention when adding posts.
103 4. **Parallel cold frames** (`drawLanesDirectParallel`): zoom-in-flight, teleport jumps, and live edit gestures can't use tiles, so those frames rasterize every lane concurrently into persistent per-lane buffers (same `drawLaneClips` code, composited on main; the storyboard lane stays on main — `BoardStore` isn't locked). Anything `drawLaneClips` touches must therefore be **thread-safe under concurrent reads**: the scene caches are read-only during a draw; `MediaPipeline`'s thumb/waveform/strip-info bookkeeping, `bakedSymbol`, and the CTLine title cache are lock-guarded; `DrawProf` only records on the main thread. `NSGraphicsContext.current` is thread-local, but `concurrentPerform` runs one iteration on the calling thread — save/restore it, never nil it.
104
105 Image assets that the timeline blits per frame (filmstrip thumbs, waveforms) are decoded once into the display's raster format via `MediaPipeline.displayImage` (BGRA premultiplied, screen colorspace) and returned as `CGImage` — a file-format NSImage costs a per-draw swizzle + colorspace conversion. Misses are negatively cached (`thumbMissing`/`waveformMissing`) so a dense timeline doesn't re-dispatch hundreds of no-op loads per frame; the miss sets clear when fresh strips/waveforms land.
106
107 Model-side lookups that draw or hit-test against clips go through the scene caches (`clipsByLane` sorted per lane + binary-searched `visibleIndexRange`, `overlapsByLane`, `overlapIdsByLane`, `cachedTimelineDuration`) — never `project.clips.filter(...)` per frame/event; `Model.overlaps()` relies on its sorted early-`break` to stay near-linear.
79108- `ViewerGridView.swift` — multicam grid, one cell per visible track plus a Fusion comps cell.
80109- `TransportBar.swift`, `ExportDialog.swift`, `ColorPicker.swift`, `Theme.swift` (light/dark, follows system appearance, no manual toggle), `Tools.swift` (tool enum + radial quick-picker).
81110
sequencer/Deltarune Ch5.sq/project.json deleted-11285
......@@ -1,11285 +0,0 @@
1{
2 "formatVersion" : 2,
3 "project" : {
4 "boardHeight" : 1080,
5 "boardWidth" : 1920,
6 "clips" : [
7 {
8 "duration" : 8.779661016949152,
9 "fadeIn" : 0,
10 "fadeOut" : 0,
11 "id" : "0856AA33-6A7E-4E96-BFB1-7B40DCC5C3E2",
12 "kind" : "video",
13 "linkId" : "E4929B8D-EE5B-4B9D-850D-B43F8A803E7A",
14 "mediaId" : "26CBEF77-6850-4D82-A5BD-A6A37587C040",
15 "muted" : false,
16 "newShot" : false,
17 "speed" : 1,
18 "srcIn" : 0.8026453438086703,
19 "start" : 0,
20 "track" : "v0"
21 },
22 {
23 "duration" : 8.779661016949152,
24 "fadeIn" : 0,
25 "fadeOut" : 0,
26 "id" : "F03A433A-DD0D-43A4-B2E0-77976661B8D5",
27 "kind" : "audio",
28 "linkId" : "E4929B8D-EE5B-4B9D-850D-B43F8A803E7A",
29 "mediaId" : "F676BA7D-A0E0-4603-AE13-B853D2F9F2BB",
30 "muted" : true,
31 "newShot" : false,
32 "speed" : 1,
33 "srcIn" : 0.9509002194553415,
34 "start" : 0,
35 "track" : "v1"
36 },
37 {
38 "duration" : 8.779661016949152,
39 "fadeIn" : 0,
40 "fadeOut" : 0,
41 "id" : "DDC04C1A-94A8-4F4D-9568-73F07B3E0304",
42 "kind" : "audio",
43 "linkId" : "E4929B8D-EE5B-4B9D-850D-B43F8A803E7A",
44 "mediaId" : "6FFA3786-8E51-4E69-A15D-D6A991C0CCAD",
45 "muted" : false,
46 "newShot" : false,
47 "speed" : 1,
48 "srcIn" : 1.2881355932203389,
49 "start" : 0,
50 "track" : "v2"
51 },
52 {
53 "duration" : 8.779661016949152,
54 "fadeIn" : 0,
55 "fadeOut" : 0,
56 "id" : "856D7E40-8EF3-4242-AB37-B26CBFD4B3F7",
57 "kind" : "video",
58 "linkId" : "E4929B8D-EE5B-4B9D-850D-B43F8A803E7A",
59 "mediaId" : "609E978E-5994-4757-A6C7-EAAE72818A49",
60 "muted" : false,
61 "newShot" : false,
62 "speed" : 1,
63 "srcIn" : 0.8603196774655755,
64 "start" : 0,
65 "track" : "v3"
66 },
67 {
68 "duration" : 8.779661016949152,
69 "fadeIn" : 0,
70 "fadeOut" : 0,
71 "id" : "ACBD774E-FC95-41F4-B5FB-037577828AD9",
72 "kind" : "video",
73 "linkId" : "E4929B8D-EE5B-4B9D-850D-B43F8A803E7A",
74 "mediaId" : "C0E05C14-2D32-4A55-9041-7A752A97F6B1",
75 "muted" : false,
76 "newShot" : false,
77 "speed" : 1,
78 "srcIn" : 0.8603078854665893,
79 "start" : 0,
80 "track" : "v4"
81 },
82 {
83 "duration" : 9.288135593220339,
84 "fadeIn" : 0,
85 "fadeOut" : 0,
86 "id" : "21E55168-059F-4CFC-AF14-FF43F7E7A9F8",
87 "kind" : "video",
88 "linkId" : "7ADB32D0-2D02-491A-8BF7-11F6A47A354E",
89 "mediaId" : "26CBEF77-6850-4D82-A5BD-A6A37587C040",
90 "muted" : false,
91 "newShot" : false,
92 "speed" : 1,
93 "srcIn" : 11.582306360757823,
94 "start" : 8.779661016949152,
95 "track" : "v0"
96 },
97 {
98 "duration" : 9.288135593220339,
99 "fadeIn" : 0,
100 "fadeOut" : 0,
101 "id" : "FA932A03-4AEE-428C-9593-EB3CFA09BEBE",
102 "kind" : "audio",
103 "linkId" : "7ADB32D0-2D02-491A-8BF7-11F6A47A354E",
104 "mediaId" : "F676BA7D-A0E0-4603-AE13-B853D2F9F2BB",
105 "muted" : true,
106 "newShot" : false,
107 "speed" : 1,
108 "srcIn" : 11.730561236404494,
109 "start" : 8.779661016949152,
110 "track" : "v1"
111 },
112 {
113 "duration" : 9.288135593220339,
114 "fadeIn" : 0,
115 "fadeOut" : 0,
116 "id" : "BDB5ABE0-71D2-457D-A7D4-21ADCC3B5743",
117 "kind" : "audio",
118 "linkId" : "7ADB32D0-2D02-491A-8BF7-11F6A47A354E",
119 "mediaId" : "6FFA3786-8E51-4E69-A15D-D6A991C0CCAD",
120 "muted" : false,
121 "newShot" : false,
122 "speed" : 1,
123 "srcIn" : 12.067796610169491,
124 "start" : 8.779661016949152,
125 "track" : "v2"
126 },
127 {
128 "duration" : 9.288135593220339,
129 "fadeIn" : 0,
130 "fadeOut" : 0,
131 "id" : "2A859930-C3F8-4E02-BD27-4CF36CF2138E",
132 "kind" : "video",
133 "linkId" : "7ADB32D0-2D02-491A-8BF7-11F6A47A354E",
134 "mediaId" : "609E978E-5994-4757-A6C7-EAAE72818A49",
135 "muted" : false,
136 "newShot" : false,
137 "speed" : 1,
138 "srcIn" : 11.639980694414728,
139 "start" : 8.779661016949152,
140 "track" : "v3"
141 },
142 {
143 "duration" : 9.288135593220339,
144 "fadeIn" : 0,
145 "fadeOut" : 0,
146 "id" : "197B76CD-FB1C-4DB5-BB64-B260CC9CC769",
147 "kind" : "video",
148 "linkId" : "7ADB32D0-2D02-491A-8BF7-11F6A47A354E",
149 "mediaId" : "C0E05C14-2D32-4A55-9041-7A752A97F6B1",
150 "muted" : false,
151 "newShot" : false,
152 "speed" : 1,
153 "srcIn" : 11.639968902415742,
154 "start" : 8.779661016949152,
155 "track" : "v4"
156 },
157 {
158 "duration" : 1.322033898305083,
159 "fadeIn" : 0,
160 "fadeOut" : 0,
161 "id" : "30FF8D20-D84E-4A9C-8303-F75C6D488959",
162 "kind" : "video",
163 "linkId" : "DE8705CE-9B40-487A-A252-50C469C56589",
164 "mediaId" : "26CBEF77-6850-4D82-A5BD-A6A37587C040",
165 "muted" : false,
166 "newShot" : false,
167 "speed" : 1,
168 "srcIn" : 86.02298432685951,
169 "start" : 18.067796610169495,
170 "track" : "v0"
171 },
172 {
173 "duration" : 1.322033898305083,
174 "fadeIn" : 0,
175 "fadeOut" : 0,
176 "id" : "28F8C52E-77E0-4886-9A6F-49E8524EFDB0",
177 "kind" : "audio",
178 "linkId" : "DE8705CE-9B40-487A-A252-50C469C56589",
179 "mediaId" : "F676BA7D-A0E0-4603-AE13-B853D2F9F2BB",
180 "muted" : true,
181 "newShot" : false,
182 "speed" : 1,
183 "srcIn" : 86.17123920250619,
184 "start" : 18.067796610169495,
185 "track" : "v1"
186 },
187 {
188 "duration" : 1.322033898305083,
189 "fadeIn" : 0,
190 "fadeOut" : 0,
191 "id" : "385FB864-B457-4585-A1E2-B07F0B398682",
192 "kind" : "audio",
193 "linkId" : "DE8705CE-9B40-487A-A252-50C469C56589",
194 "mediaId" : "6FFA3786-8E51-4E69-A15D-D6A991C0CCAD",
195 "muted" : false,
196 "newShot" : false,
197 "speed" : 1,
198 "srcIn" : 86.50847457627118,
199 "start" : 18.067796610169495,
200 "track" : "v2"
201 },
202 {
203 "duration" : 1.322033898305083,
204 "fadeIn" : 0,
205 "fadeOut" : 0,
206 "id" : "4703FBC9-8E06-4DE3-A460-19695096A67B",
207 "kind" : "video",
208 "linkId" : "DE8705CE-9B40-487A-A252-50C469C56589",
209 "mediaId" : "609E978E-5994-4757-A6C7-EAAE72818A49",
210 "muted" : false,
211 "newShot" : false,
212 "speed" : 1,
213 "srcIn" : 86.08065866051642,
214 "start" : 18.067796610169495,
215 "track" : "v3"
216 },
217 {
218 "duration" : 1.322033898305083,
219 "fadeIn" : 0,
220 "fadeOut" : 0,
221 "id" : "14297849-4D4F-41D8-A59A-15B5B1D60DF2",
222 "kind" : "video",
223 "linkId" : "DE8705CE-9B40-487A-A252-50C469C56589",
224 "mediaId" : "C0E05C14-2D32-4A55-9041-7A752A97F6B1",
225 "muted" : false,
226 "newShot" : false,
227 "speed" : 1,
228 "srcIn" : 86.08064686851743,
229 "start" : 18.067796610169495,
230 "track" : "v4"
231 },
232 {
233 "duration" : 1.1525423728813529,
234 "fadeIn" : 0,
235 "fadeOut" : 0,
236 "id" : "5EF423FA-AD82-4C89-8385-335325345015",
237 "kind" : "video",
238 "linkId" : "66157B6F-FF8D-490F-B510-26417942AA98",
239 "mediaId" : "26CBEF77-6850-4D82-A5BD-A6A37587C040",
240 "muted" : false,
241 "newShot" : false,
242 "speed" : 1,
243 "srcIn" : 113.98908602177477,
244 "start" : 19.389830508474578,
245 "track" : "v0"
246 },
247 {
248 "duration" : 1.1525423728813529,
249 "fadeIn" : 0,
250 "fadeOut" : 0,
251 "id" : "95F0F7BD-FA8B-48FE-A975-859CBDE2F60A",
252 "kind" : "audio",
253 "linkId" : "66157B6F-FF8D-490F-B510-26417942AA98",
254 "mediaId" : "F676BA7D-A0E0-4603-AE13-B853D2F9F2BB",
255 "muted" : true,
256 "newShot" : false,
257 "speed" : 1,
258 "srcIn" : 114.13734089742144,
259 "start" : 19.389830508474578,
260 "track" : "v1"
261 },
262 {
263 "duration" : 1.1525423728813529,
264 "fadeIn" : 0,
265 "fadeOut" : 0,
266 "id" : "236F5693-66F0-4DF2-9C6B-0FC10DC039A0",
267 "kind" : "audio",
268 "linkId" : "66157B6F-FF8D-490F-B510-26417942AA98",
269 "mediaId" : "6FFA3786-8E51-4E69-A15D-D6A991C0CCAD",
270 "muted" : false,
271 "newShot" : false,
272 "speed" : 1,
273 "srcIn" : 114.47457627118644,
274 "start" : 19.389830508474578,
275 "track" : "v2"
276 },
277 {
278 "duration" : 1.1525423728813529,
279 "fadeIn" : 0,
280 "fadeOut" : 0,
281 "id" : "08DF237F-434A-4ADB-B77C-F165BA3B9BAE",
282 "kind" : "video",
283 "linkId" : "66157B6F-FF8D-490F-B510-26417942AA98",
284 "mediaId" : "609E978E-5994-4757-A6C7-EAAE72818A49",
285 "muted" : false,
286 "newShot" : false,
287 "speed" : 1,
288 "srcIn" : 114.04676035543167,
289 "start" : 19.389830508474578,
290 "track" : "v3"
291 },
292 {
293 "duration" : 1.1525423728813529,
294 "fadeIn" : 0,
295 "fadeOut" : 0,
296 "id" : "6CEFB0C1-9FA6-4727-B291-AE95CBF63C1E",
297 "kind" : "video",
298 "linkId" : "66157B6F-FF8D-490F-B510-26417942AA98",
299 "mediaId" : "C0E05C14-2D32-4A55-9041-7A752A97F6B1",
300 "muted" : false,
301 "newShot" : false,
302 "speed" : 1,
303 "srcIn" : 114.04674856343269,
304 "start" : 19.389830508474578,
305 "track" : "v4"
306 },
307 {
308 "duration" : 5.220338983050851,
309 "fadeIn" : 0,
310 "fadeOut" : 0,
311 "id" : "CBC234E0-371D-4FDA-99B5-0BB7DE1737EB",
312 "kind" : "video",
313 "linkId" : "CEA52F0A-38E4-4F7B-AB96-117218501790",
314 "mediaId" : "26CBEF77-6850-4D82-A5BD-A6A37587C040",
315 "muted" : false,
316 "newShot" : false,
317 "speed" : 1,
318 "srcIn" : 397.9212894116052,
319 "start" : 20.54237288135596,
320 "track" : "v0"
321 },
322 {
323 "duration" : 5.220338983050851,
324 "fadeIn" : 0,
325 "fadeOut" : 0,
326 "id" : "A4F89301-A2DE-4597-AD9E-DC51F3507355",
327 "kind" : "audio",
328 "linkId" : "CEA52F0A-38E4-4F7B-AB96-117218501790",
329 "mediaId" : "F676BA7D-A0E0-4603-AE13-B853D2F9F2BB",
330 "muted" : true,
331 "newShot" : false,
332 "speed" : 1,
333 "srcIn" : 398.06954428725186,
334 "start" : 20.54237288135596,
335 "track" : "v1"
336 },
337 {
338 "duration" : 5.220338983050851,
339 "fadeIn" : 0,
340 "fadeOut" : 0,
341 "id" : "F00A71B2-649B-4BF6-9C0A-80270331C353",
342 "kind" : "audio",
343 "linkId" : "CEA52F0A-38E4-4F7B-AB96-117218501790",
344 "mediaId" : "6FFA3786-8E51-4E69-A15D-D6A991C0CCAD",
345 "muted" : false,
346 "newShot" : false,
347 "speed" : 1,
348 "srcIn" : 398.40677966101686,
349 "start" : 20.54237288135596,
350 "track" : "v2"
351 },
352 {
353 "duration" : 5.220338983050851,
354 "fadeIn" : 0,
355 "fadeOut" : 0,
356 "id" : "31A4F820-D7C2-4CAB-8A20-F52F615C6114",
357 "kind" : "video",
358 "linkId" : "CEA52F0A-38E4-4F7B-AB96-117218501790",
359 "mediaId" : "609E978E-5994-4757-A6C7-EAAE72818A49",
360 "muted" : false,
361 "newShot" : false,
362 "speed" : 1,
363 "srcIn" : 397.9789637452621,
364 "start" : 20.54237288135596,
365 "track" : "v3"
366 },
367 {
368 "duration" : 5.220338983050851,
369 "fadeIn" : 0,
370 "fadeOut" : 0,
371 "id" : "4B6EF408-0BAE-4495-885B-A0281AFD0B46",
372 "kind" : "video",
373 "linkId" : "CEA52F0A-38E4-4F7B-AB96-117218501790",
374 "mediaId" : "C0E05C14-2D32-4A55-9041-7A752A97F6B1",
375 "muted" : false,
376 "newShot" : false,
377 "speed" : 1,
378 "srcIn" : 397.9789519532631,
379 "start" : 20.54237288135596,
380 "track" : "v4"
381 },
382 {
383 "duration" : 14.57627118644065,
384 "fadeIn" : 0,
385 "fadeOut" : 0,
386 "id" : "B386A5EC-7B75-421C-941E-9A2E8FC4C303",
387 "kind" : "video",
388 "linkId" : "3688DE28-A2CB-4ED5-9666-9772A7FAF5FD",
389 "mediaId" : "26CBEF77-6850-4D82-A5BD-A6A37587C040",
390 "muted" : false,
391 "newShot" : false,
392 "speed" : 1,
393 "srcIn" : 422.73484873363907,
394 "start" : 25.76271186440681,
395 "track" : "v0"
396 },
397 {
398 "duration" : 14.57627118644065,
399 "fadeIn" : 0,
400 "fadeOut" : 0,
401 "id" : "78D95643-9E92-4665-A49E-1640916F222F",
402 "kind" : "audio",
403 "linkId" : "3688DE28-A2CB-4ED5-9666-9772A7FAF5FD",
404 "mediaId" : "F676BA7D-A0E0-4603-AE13-B853D2F9F2BB",
405 "muted" : true,
406 "newShot" : false,
407 "speed" : 1,
408 "srcIn" : 422.88310360928574,
409 "start" : 25.76271186440681,
410 "track" : "v1"
411 },
412 {
413 "duration" : 14.57627118644065,
414 "fadeIn" : 0,
415 "fadeOut" : 0,
416 "id" : "3EC46637-04E5-407D-90BD-BB290FB532C6",
417 "kind" : "audio",
418 "linkId" : "3688DE28-A2CB-4ED5-9666-9772A7FAF5FD",
419 "mediaId" : "6FFA3786-8E51-4E69-A15D-D6A991C0CCAD",
420 "muted" : false,
421 "newShot" : false,
422 "speed" : 1,
423 "srcIn" : 423.22033898305074,
424 "start" : 25.76271186440681,
425 "track" : "v2"
426 },
427 {
428 "duration" : 14.57627118644065,
429 "fadeIn" : 0,
430 "fadeOut" : 0,
431 "id" : "54D04272-5607-4E88-BBAB-A50A9FEAA953",
432 "kind" : "video",
433 "linkId" : "3688DE28-A2CB-4ED5-9666-9772A7FAF5FD",
434 "mediaId" : "609E978E-5994-4757-A6C7-EAAE72818A49",
435 "muted" : false,
436 "newShot" : false,
437 "speed" : 1,
438 "srcIn" : 422.792523067296,
439 "start" : 25.76271186440681,
440 "track" : "v3"
441 },
442 {
443 "duration" : 14.57627118644065,
444 "fadeIn" : 0,
445 "fadeOut" : 0,
446 "id" : "7B34B91A-3E53-4B21-A7C7-22172D7442EA",
447 "kind" : "video",
448 "linkId" : "3688DE28-A2CB-4ED5-9666-9772A7FAF5FD",
449 "mediaId" : "C0E05C14-2D32-4A55-9041-7A752A97F6B1",
450 "muted" : false,
451 "newShot" : false,
452 "speed" : 1,
453 "srcIn" : 422.792511275297,
454 "start" : 25.76271186440681,
455 "track" : "v4"
456 },
457 {
458 "duration" : 10.644067796610166,
459 "fadeIn" : 0,
460 "fadeOut" : 0,
461 "id" : "3EDF6E35-1C81-4706-878F-E685962438E7",
462 "kind" : "video",
463 "linkId" : "E6380C0A-8BCB-4E01-A3FF-4B6CE609EBA7",
464 "mediaId" : "26CBEF77-6850-4D82-A5BD-A6A37587C040",
465 "muted" : false,
466 "newShot" : false,
467 "speed" : 1,
468 "srcIn" : 447.0399334794017,
469 "start" : 40.33898305084746,
470 "track" : "v0"
471 },
472 {
473 "duration" : 10.644067796610166,
474 "fadeIn" : 0,
475 "fadeOut" : 0,
476 "id" : "5E3BFA57-DA8E-47B2-969D-026F31AD2A3C",
477 "kind" : "audio",
478 "linkId" : "E6380C0A-8BCB-4E01-A3FF-4B6CE609EBA7",
479 "mediaId" : "F676BA7D-A0E0-4603-AE13-B853D2F9F2BB",
480 "muted" : true,
481 "newShot" : false,
482 "speed" : 1,
483 "srcIn" : 447.1881883550484,
484 "start" : 40.33898305084746,
485 "track" : "v1"
486 },
487 {
488 "duration" : 10.644067796610166,
489 "fadeIn" : 0,
490 "fadeOut" : 0,
491 "id" : "8E2AD73C-7F9E-4F46-8D04-EF9087EE690A",
492 "kind" : "audio",
493 "linkId" : "E6380C0A-8BCB-4E01-A3FF-4B6CE609EBA7",
494 "mediaId" : "6FFA3786-8E51-4E69-A15D-D6A991C0CCAD",
495 "muted" : false,
496 "newShot" : false,
497 "speed" : 1,
498 "srcIn" : 447.5254237288134,
499 "start" : 40.33898305084746,
500 "track" : "v2"
501 },
502 {
503 "duration" : 10.644067796610166,
504 "fadeIn" : 0,
505 "fadeOut" : 0,
506 "id" : "F54DE68B-6E96-4BA9-BEB6-81E4B984892C",
507 "kind" : "video",
508 "linkId" : "E6380C0A-8BCB-4E01-A3FF-4B6CE609EBA7",
509 "mediaId" : "609E978E-5994-4757-A6C7-EAAE72818A49",
510 "muted" : false,
511 "newShot" : false,
512 "speed" : 1,
513 "srcIn" : 447.09760781305863,
514 "start" : 40.33898305084746,
515 "track" : "v3"
516 },
517 {
518 "duration" : 10.644067796610166,
519 "fadeIn" : 0,
520 "fadeOut" : 0,
521 "id" : "C77A294D-6278-4BAD-8BC5-F7BC3C372A00",
522 "kind" : "video",
523 "linkId" : "E6380C0A-8BCB-4E01-A3FF-4B6CE609EBA7",
524 "mediaId" : "C0E05C14-2D32-4A55-9041-7A752A97F6B1",
525 "muted" : false,
526 "newShot" : false,
527 "speed" : 1,
528 "srcIn" : 447.09759602105964,
529 "start" : 40.33898305084746,
530 "track" : "v4"
531 },
532 {
533 "duration" : 9.694915254237287,
534 "fadeIn" : 0,
535 "fadeOut" : 0,
536 "id" : "BA4DCFDD-E47E-4485-86C7-5D2F91DAD82A",
537 "kind" : "video",
538 "linkId" : "6CC9888B-E11F-41E5-BAC2-A15C7D015557",
539 "mediaId" : "26CBEF77-6850-4D82-A5BD-A6A37587C040",
540 "muted" : false,
541 "newShot" : false,
542 "speed" : 1,
543 "srcIn" : 495.4128148353339,
544 "start" : 50.983050847457626,
545 "track" : "v0"
546 },
547 {
548 "duration" : 9.694915254237287,
549 "fadeIn" : 0,
550 "fadeOut" : 0,
551 "id" : "F7D2760F-4578-4319-9C36-40A5E0C0554A",
552 "kind" : "audio",
553 "linkId" : "6CC9888B-E11F-41E5-BAC2-A15C7D015557",
554 "mediaId" : "F676BA7D-A0E0-4603-AE13-B853D2F9F2BB",
555 "muted" : true,
556 "newShot" : false,
557 "speed" : 1,
558 "srcIn" : 495.5610697109806,
559 "start" : 50.983050847457626,
560 "track" : "v1"
561 },
562 {
563 "duration" : 9.694915254237287,
564 "fadeIn" : 0,
565 "fadeOut" : 0,
566 "id" : "344797CD-1399-4A2F-B5D4-ED0DAFF921B2",
567 "kind" : "audio",
568 "linkId" : "6CC9888B-E11F-41E5-BAC2-A15C7D015557",
569 "mediaId" : "6FFA3786-8E51-4E69-A15D-D6A991C0CCAD",
570 "muted" : false,
571 "newShot" : false,
572 "speed" : 1,
573 "srcIn" : 495.8983050847456,
574 "start" : 50.983050847457626,
575 "track" : "v2"
576 },
577 {
578 "duration" : 9.694915254237287,
579 "fadeIn" : 0,
580 "fadeOut" : 0,
581 "id" : "436EE031-3821-460A-B1E6-362C6AC7DD15",
582 "kind" : "video",
583 "linkId" : "6CC9888B-E11F-41E5-BAC2-A15C7D015557",
584 "mediaId" : "609E978E-5994-4757-A6C7-EAAE72818A49",
585 "muted" : false,
586 "newShot" : false,
587 "speed" : 1,
588 "srcIn" : 495.4704891689908,
589 "start" : 50.983050847457626,
590 "track" : "v3"
591 },
592 {
593 "duration" : 9.694915254237287,
594 "fadeIn" : 0,
595 "fadeOut" : 0,
596 "id" : "DA7C94D5-26B6-465A-958E-DA029F1CEC3F",
597 "kind" : "video",
598 "linkId" : "6CC9888B-E11F-41E5-BAC2-A15C7D015557",
599 "mediaId" : "C0E05C14-2D32-4A55-9041-7A752A97F6B1",
600 "muted" : false,
601 "newShot" : false,
602 "speed" : 1,
603 "srcIn" : 495.4704773769918,
604 "start" : 50.983050847457626,
605 "track" : "v4"
606 },
607 {
608 "duration" : 12.440677966101696,
609 "fadeIn" : 0,
610 "fadeOut" : 0,
611 "id" : "EF8B18B2-7BEB-4520-9C17-903C1A223C06",
612 "kind" : "video",
613 "linkId" : "8D7F9D29-68E0-4E62-B7D7-B149575B3269",
614 "mediaId" : "26CBEF77-6850-4D82-A5BD-A6A37587C040",
615 "muted" : false,
616 "newShot" : false,
617 "speed" : 1,
618 "srcIn" : 511.0399334794017,
619 "start" : 60.67796610169491,
620 "track" : "v0"
621 },
622 {
623 "duration" : 12.440677966101696,
624 "fadeIn" : 0,
625 "fadeOut" : 0,
626 "id" : "65C8245D-4AA2-4534-BDAF-3D2841DC3F70",
627 "kind" : "audio",
628 "linkId" : "8D7F9D29-68E0-4E62-B7D7-B149575B3269",
629 "mediaId" : "F676BA7D-A0E0-4603-AE13-B853D2F9F2BB",
630 "muted" : true,
631 "newShot" : false,
632 "speed" : 1,
633 "srcIn" : 511.1881883550484,
634 "start" : 60.67796610169491,
635 "track" : "v1"
636 },
637 {
638 "duration" : 12.440677966101696,
639 "fadeIn" : 0,
640 "fadeOut" : 0,
641 "id" : "8ECF7044-2C74-43E6-9219-036DB453AA8E",
642 "kind" : "audio",
643 "linkId" : "8D7F9D29-68E0-4E62-B7D7-B149575B3269",
644 "mediaId" : "6FFA3786-8E51-4E69-A15D-D6A991C0CCAD",
645 "muted" : false,
646 "newShot" : false,
647 "speed" : 1,
648 "srcIn" : 511.5254237288134,
649 "start" : 60.67796610169491,
650 "track" : "v2"
651 },
652 {
653 "duration" : 12.440677966101696,
654 "fadeIn" : 0,
655 "fadeOut" : 0,
656 "id" : "E0691830-C2E3-4692-A443-C96E79034416",
657 "kind" : "video",
658 "linkId" : "8D7F9D29-68E0-4E62-B7D7-B149575B3269",
659 "mediaId" : "609E978E-5994-4757-A6C7-EAAE72818A49",
660 "muted" : false,
661 "newShot" : false,
662 "speed" : 1,
663 "srcIn" : 511.09760781305863,
664 "start" : 60.67796610169491,
665 "track" : "v3"
666 },
667 {
668 "duration" : 12.440677966101696,
669 "fadeIn" : 0,
670 "fadeOut" : 0,
671 "id" : "1DF28AA1-85FC-4AA6-8BCC-86CF38B06C1B",
672 "kind" : "video",
673 "linkId" : "8D7F9D29-68E0-4E62-B7D7-B149575B3269",
674 "mediaId" : "C0E05C14-2D32-4A55-9041-7A752A97F6B1",
675 "muted" : false,
676 "newShot" : false,
677 "speed" : 1,
678 "srcIn" : 511.09759602105964,
679 "start" : 60.67796610169491,
680 "track" : "v4"
681 },
682 {
683 "duration" : 2.0677966101694807,
684 "fadeIn" : 0,
685 "fadeOut" : 0,
686 "id" : "4937BF52-96D7-4AF0-B222-22E27B797A5C",
687 "kind" : "video",
688 "linkId" : "E5D93442-8B82-4040-8E5F-BD717FD75859",
689 "mediaId" : "26CBEF77-6850-4D82-A5BD-A6A37587C040",
690 "muted" : false,
691 "newShot" : false,
692 "speed" : 1,
693 "srcIn" : 539.9212894116051,
694 "start" : 73.11864406779661,
695 "track" : "v0"
696 },
697 {
698 "duration" : 2.0677966101694807,
699 "fadeIn" : 0,
700 "fadeOut" : 0,
701 "id" : "16FFB49E-AD0E-430F-BC1E-0E649D37B469",
702 "kind" : "audio",
703 "linkId" : "E5D93442-8B82-4040-8E5F-BD717FD75859",
704 "mediaId" : "F676BA7D-A0E0-4603-AE13-B853D2F9F2BB",
705 "muted" : true,
706 "newShot" : false,
707 "speed" : 1,
708 "srcIn" : 540.0695442872518,
709 "start" : 73.11864406779661,
710 "track" : "v1"
711 },
712 {
713 "duration" : 2.0677966101694807,
714 "fadeIn" : 0,
715 "fadeOut" : 0,
716 "id" : "3772DE62-3FC2-491B-9DBC-922F6DDC5BB1",
717 "kind" : "audio",
718 "linkId" : "E5D93442-8B82-4040-8E5F-BD717FD75859",
719 "mediaId" : "6FFA3786-8E51-4E69-A15D-D6A991C0CCAD",
720 "muted" : false,
721 "newShot" : false,
722 "speed" : 1,
723 "srcIn" : 540.4067796610168,
724 "start" : 73.11864406779661,
725 "track" : "v2"
726 },
727 {
728 "duration" : 2.0677966101694807,
729 "fadeIn" : 0,
730 "fadeOut" : 0,
731 "id" : "4A62F7EC-EFF6-4CE8-82FA-024FC1AE5421",
732 "kind" : "video",
733 "linkId" : "E5D93442-8B82-4040-8E5F-BD717FD75859",
734 "mediaId" : "609E978E-5994-4757-A6C7-EAAE72818A49",
735 "muted" : false,
736 "newShot" : false,
737 "speed" : 1,
738 "srcIn" : 539.978963745262,
739 "start" : 73.11864406779661,
740 "track" : "v3"
741 },
742 {
743 "duration" : 2.0677966101694807,
744 "fadeIn" : 0,
745 "fadeOut" : 0,
746 "id" : "7E5889B3-52E4-48AD-8E76-DCAC57E2F26C",
747 "kind" : "video",
748 "linkId" : "E5D93442-8B82-4040-8E5F-BD717FD75859",
749 "mediaId" : "C0E05C14-2D32-4A55-9041-7A752A97F6B1",
750 "muted" : false,
751 "newShot" : false,
752 "speed" : 1,
753 "srcIn" : 539.978951953263,
754 "start" : 73.11864406779661,
755 "track" : "v4"
756 },
757 {
758 "duration" : 4.542372881355931,
759 "fadeIn" : 0,
760 "fadeOut" : 0,
761 "id" : "779C851A-2F3C-4B84-8EDB-99C3D657B311",
762 "kind" : "video",
763 "linkId" : "DCA0DA82-D677-4F94-8ECD-EC2EE408A444",
764 "mediaId" : "26CBEF77-6850-4D82-A5BD-A6A37587C040",
765 "muted" : false,
766 "newShot" : false,
767 "speed" : 1,
768 "srcIn" : 552.0229843268594,
769 "start" : 75.18644067796609,
770 "track" : "v0"
771 },
772 {
773 "duration" : 4.542372881355931,
774 "fadeIn" : 0,
775 "fadeOut" : 0,
776 "id" : "6897606B-DA0A-4825-B6DB-325E170FC9B9",
777 "kind" : "audio",
778 "linkId" : "DCA0DA82-D677-4F94-8ECD-EC2EE408A444",
779 "mediaId" : "F676BA7D-A0E0-4603-AE13-B853D2F9F2BB",
780 "muted" : true,
781 "newShot" : false,
782 "speed" : 1,
783 "srcIn" : 552.1712392025061,
784 "start" : 75.18644067796609,
785 "track" : "v1"
786 },
787 {
788 "duration" : 4.542372881355931,
789 "fadeIn" : 0,
790 "fadeOut" : 0,
791 "id" : "07934764-AD42-4089-80B6-B244015447CF",
792 "kind" : "audio",
793 "linkId" : "DCA0DA82-D677-4F94-8ECD-EC2EE408A444",
794 "mediaId" : "6FFA3786-8E51-4E69-A15D-D6A991C0CCAD",
795 "muted" : false,
796 "newShot" : false,
797 "speed" : 1,
798 "srcIn" : 552.508474576271,
799 "start" : 75.18644067796609,
800 "track" : "v2"
801 },
802 {
803 "duration" : 4.542372881355931,
804 "fadeIn" : 0,
805 "fadeOut" : 0,
806 "id" : "EE2670C2-1AAA-40FC-A411-8CE33185992E",
807 "kind" : "video",
808 "linkId" : "DCA0DA82-D677-4F94-8ECD-EC2EE408A444",
809 "mediaId" : "609E978E-5994-4757-A6C7-EAAE72818A49",
810 "muted" : false,
811 "newShot" : false,
812 "speed" : 1,
813 "srcIn" : 552.0806586605163,
814 "start" : 75.18644067796609,
815 "track" : "v3"
816 },
817 {
818 "duration" : 4.542372881355931,
819 "fadeIn" : 0,
820 "fadeOut" : 0,
821 "id" : "83DC8C97-0117-495D-AB6F-488575028DF6",
822 "kind" : "video",
823 "linkId" : "DCA0DA82-D677-4F94-8ECD-EC2EE408A444",
824 "mediaId" : "C0E05C14-2D32-4A55-9041-7A752A97F6B1",
825 "muted" : false,
826 "newShot" : false,
827 "speed" : 1,
828 "srcIn" : 552.0806468685173,
829 "start" : 75.18644067796609,
830 "track" : "v4"
831 },
832 {
833 "duration" : 2.542372881355945,
834 "fadeIn" : 0,
835 "fadeOut" : 0,
836 "id" : "75DD8465-3E74-4576-A287-6F0B7A25885F",
837 "kind" : "video",
838 "linkId" : "B3514666-D79A-43F4-96AF-77F14857D9AE",
839 "mediaId" : "26CBEF77-6850-4D82-A5BD-A6A37587C040",
840 "muted" : false,
841 "newShot" : false,
842 "speed" : 1,
843 "srcIn" : 565.1077300895713,
844 "start" : 79.76271186440677,
845 "track" : "v0"
846 },
847 {
848 "duration" : 2.542372881355945,
849 "fadeIn" : 0,
850 "fadeOut" : 0,
851 "id" : "B5BC5C0D-5C8F-4616-954E-F0544734C4CC",
852 "kind" : "audio",
853 "linkId" : "B3514666-D79A-43F4-96AF-77F14857D9AE",
854 "mediaId" : "F676BA7D-A0E0-4603-AE13-B853D2F9F2BB",
855 "muted" : true,
856 "newShot" : false,
857 "speed" : 1,
858 "srcIn" : 565.255984965218,
859 "start" : 79.76271186440677,
860 "track" : "v1"
861 },
862 {
863 "duration" : 2.542372881355945,
864 "fadeIn" : 0,
865 "fadeOut" : 0,
866 "id" : "743266F1-8759-428D-8500-328A33B14EE7",
867 "kind" : "audio",
868 "linkId" : "B3514666-D79A-43F4-96AF-77F14857D9AE",
869 "mediaId" : "6FFA3786-8E51-4E69-A15D-D6A991C0CCAD",
870 "muted" : false,
871 "newShot" : false,
872 "speed" : 1,
873 "srcIn" : 565.593220338983,
874 "start" : 79.76271186440677,
875 "track" : "v2"
876 },
877 {
878 "duration" : 2.542372881355945,
879 "fadeIn" : 0,
880 "fadeOut" : 0,
881 "id" : "99C8EB5C-633C-407A-B29D-0F563C4A2A11",
882 "kind" : "video",
883 "linkId" : "B3514666-D79A-43F4-96AF-77F14857D9AE",
884 "mediaId" : "609E978E-5994-4757-A6C7-EAAE72818A49",
885 "muted" : false,
886 "newShot" : false,
887 "speed" : 1,
888 "srcIn" : 565.1654044232282,
889 "start" : 79.76271186440677,
890 "track" : "v3"
891 },
892 {
893 "duration" : 2.542372881355945,
894 "fadeIn" : 0,
895 "fadeOut" : 0,
896 "id" : "F5A5A67A-1C13-4920-94A3-D63379305184",
897 "kind" : "video",
898 "linkId" : "B3514666-D79A-43F4-96AF-77F14857D9AE",
899 "mediaId" : "C0E05C14-2D32-4A55-9041-7A752A97F6B1",
900 "muted" : false,
901 "newShot" : false,
902 "speed" : 1,
903 "srcIn" : 565.1653926312292,
904 "start" : 79.76271186440677,
905 "track" : "v4"
906 },
907 {
908 "duration" : 7.457627118644069,
909 "fadeIn" : 0,
910 "fadeOut" : 0,
911 "id" : "C4F9A637-30B0-4050-B75E-6C87C85699A8",
912 "kind" : "video",
913 "linkId" : "E6C60A60-B509-4E76-A1F1-797378C6C7EF",
914 "mediaId" : "26CBEF77-6850-4D82-A5BD-A6A37587C040",
915 "muted" : false,
916 "newShot" : false,
917 "speed" : 1,
918 "srcIn" : 792.0229843268595,
919 "start" : 82.30508474576271,
920 "track" : "v0"
921 },
922 {
923 "duration" : 7.457627118644069,
924 "fadeIn" : 0,
925 "fadeOut" : 0,
926 "id" : "D0EF66D9-DF73-4099-BAA1-CD7E3DC03441",
927 "kind" : "audio",
928 "linkId" : "E6C60A60-B509-4E76-A1F1-797378C6C7EF",
929 "mediaId" : "F676BA7D-A0E0-4603-AE13-B853D2F9F2BB",
930 "muted" : true,
931 "newShot" : false,
932 "speed" : 1,
933 "srcIn" : 792.1712392025062,
934 "start" : 82.30508474576271,
935 "track" : "v1"
936 },
937 {
938 "duration" : 7.457627118644069,
939 "fadeIn" : 0,
940 "fadeOut" : 0,
941 "id" : "FACBA48B-CB80-4E8C-AAF6-55705F37FDB8",
942 "kind" : "audio",
943 "linkId" : "E6C60A60-B509-4E76-A1F1-797378C6C7EF",
944 "mediaId" : "6FFA3786-8E51-4E69-A15D-D6A991C0CCAD",
945 "muted" : false,
946 "newShot" : false,
947 "speed" : 1,
948 "srcIn" : 792.5084745762712,
949 "start" : 82.30508474576271,
950 "track" : "v2"
951 },
952 {
953 "duration" : 7.457627118644069,
954 "fadeIn" : 0,
955 "fadeOut" : 0,
956 "id" : "24F56A9C-9D34-412D-AAFA-816D98E1BF4C",
957 "kind" : "video",
958 "linkId" : "E6C60A60-B509-4E76-A1F1-797378C6C7EF",
959 "mediaId" : "609E978E-5994-4757-A6C7-EAAE72818A49",
960 "muted" : false,
961 "newShot" : false,
962 "speed" : 1,
963 "srcIn" : 792.0806586605164,
964 "start" : 82.30508474576271,
965 "track" : "v3"
966 },
967 {
968 "duration" : 7.457627118644069,
969 "fadeIn" : 0,
970 "fadeOut" : 0,
971 "id" : "E8BDB4FD-1478-46C9-B2B9-4A995785C804",
972 "kind" : "video",
973 "linkId" : "E6C60A60-B509-4E76-A1F1-797378C6C7EF",
974 "mediaId" : "C0E05C14-2D32-4A55-9041-7A752A97F6B1",
975 "muted" : false,
976 "newShot" : false,
977 "speed" : 1,
978 "srcIn" : 792.0806468685174,
979 "start" : 82.30508474576271,
980 "track" : "v4"
981 },
982 {
983 "duration" : 5.7966101694915295,
984 "fadeIn" : 0,
985 "fadeOut" : 0,
986 "id" : "9F719822-EE9F-4D98-AE6F-95832502501D",
987 "kind" : "video",
988 "linkId" : "33DE57F6-49E9-47DA-971D-A7E3824D9B47",
989 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
990 "muted" : false,
991 "newShot" : false,
992 "speed" : 1,
993 "srcIn" : 6.203389830508485,
994 "start" : 89.76271186440678,
995 "track" : "v0"
996 },
997 {
998 "duration" : 5.7966101694915295,
999 "fadeIn" : 0,
1000 "fadeOut" : 0,
1001 "id" : "4D903E13-8AAE-4EED-9E78-EB2D6201EE84",
1002 "kind" : "audio",
1003 "linkId" : "33DE57F6-49E9-47DA-971D-A7E3824D9B47",
1004 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
1005 "muted" : true,
1006 "newShot" : false,
1007 "speed" : 1,
1008 "srcIn" : 6.1195182891674165,
1009 "start" : 89.76271186440678,
1010 "track" : "v1"
1011 },
1012 {
1013 "duration" : 5.7966101694915295,
1014 "fadeIn" : 0,
1015 "fadeOut" : 0,
1016 "id" : "0A6A0000-76C4-4F4F-A5EC-B9DB5AA10EE5",
1017 "kind" : "audio",
1018 "linkId" : "33DE57F6-49E9-47DA-971D-A7E3824D9B47",
1019 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
1020 "muted" : false,
1021 "newShot" : false,
1022 "speed" : 1,
1023 "srcIn" : 6.166194592398341,
1024 "start" : 89.76271186440678,
1025 "track" : "v2"
1026 },
1027 {
1028 "duration" : 5.7966101694915295,
1029 "fadeIn" : 0,
1030 "fadeOut" : 0,
1031 "id" : "91FC4279-5D23-44A0-BA93-5BC8CFB0775C",
1032 "kind" : "video",
1033 "linkId" : "33DE57F6-49E9-47DA-971D-A7E3824D9B47",
1034 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
1035 "muted" : false,
1036 "newShot" : false,
1037 "speed" : 1,
1038 "srcIn" : 6.029587456170219,
1039 "start" : 89.76271186440678,
1040 "track" : "v3"
1041 },
1042 {
1043 "duration" : 5.7966101694915295,
1044 "fadeIn" : 0,
1045 "fadeOut" : 0,
1046 "id" : "51B251E0-3404-4CAC-B18A-37A29B9BF96B",
1047 "kind" : "video",
1048 "linkId" : "33DE57F6-49E9-47DA-971D-A7E3824D9B47",
1049 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
1050 "muted" : false,
1051 "newShot" : false,
1052 "speed" : 1,
1053 "srcIn" : 6.029582872171403,
1054 "start" : 89.76271186440678,
1055 "track" : "v4"
1056 },
1057 {
1058 "duration" : 18.847457627118715,
1059 "fadeIn" : 0,
1060 "fadeOut" : 0,
1061 "id" : "E8DC76EC-C4D2-46AE-853A-A60F4F0D2E07",
1062 "kind" : "video",
1063 "linkId" : "C5B94050-9128-423F-8A3C-F610A15ECBE8",
1064 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
1065 "muted" : false,
1066 "newShot" : false,
1067 "speed" : 1,
1068 "srcIn" : 153.08474576271186,
1069 "start" : 116.81355932203384,
1070 "track" : "v0"
1071 },
1072 {
1073 "duration" : 18.847457627118715,
1074 "fadeIn" : 0,
1075 "fadeOut" : 0,
1076 "id" : "4440B25F-48FC-49CC-9A78-207D16DDCCB7",
1077 "kind" : "audio",
1078 "linkId" : "C5B94050-9128-423F-8A3C-F610A15ECBE8",
1079 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
1080 "muted" : true,
1081 "newShot" : false,
1082 "speed" : 1,
1083 "srcIn" : 153.0008742213708,
1084 "start" : 116.81355932203384,
1085 "track" : "v1"
1086 },
1087 {
1088 "duration" : 18.847457627118715,
1089 "fadeIn" : 0,
1090 "fadeOut" : 0,
1091 "id" : "6FEFD05B-C857-4EBE-9EE7-F0EAAA08569F",
1092 "kind" : "audio",
1093 "linkId" : "C5B94050-9128-423F-8A3C-F610A15ECBE8",
1094 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
1095 "muted" : false,
1096 "newShot" : false,
1097 "speed" : 1,
1098 "srcIn" : 153.04755052460172,
1099 "start" : 116.81355932203384,
1100 "track" : "v2"
1101 },
1102 {
1103 "duration" : 18.847457627118715,
1104 "fadeIn" : 0,
1105 "fadeOut" : 0,
1106 "id" : "E1C8762B-5477-4769-BD47-2DE3F3E7F08A",
1107 "kind" : "video",
1108 "linkId" : "C5B94050-9128-423F-8A3C-F610A15ECBE8",
1109 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
1110 "muted" : false,
1111 "newShot" : false,
1112 "speed" : 1,
1113 "srcIn" : 152.9109433883736,
1114 "start" : 116.81355932203384,
1115 "track" : "v3"
1116 },
1117 {
1118 "duration" : 18.847457627118715,
1119 "fadeIn" : 0,
1120 "fadeOut" : 0,
1121 "id" : "6C90A7B0-6891-4C26-9ACE-E78062706047",
1122 "kind" : "video",
1123 "linkId" : "C5B94050-9128-423F-8A3C-F610A15ECBE8",
1124 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
1125 "muted" : false,
1126 "newShot" : false,
1127 "speed" : 1,
1128 "srcIn" : 152.91093880437478,
1129 "start" : 116.81355932203384,
1130 "track" : "v4"
1131 },
1132 {
1133 "duration" : 13.220338983050851,
1134 "fadeIn" : 0,
1135 "fadeOut" : 0,
1136 "id" : "BE66E066-F241-46FE-8D9A-606DBA9554B7",
1137 "kind" : "video",
1138 "linkId" : "17FC9151-B113-420C-8754-353E10218637",
1139 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
1140 "muted" : false,
1141 "newShot" : false,
1142 "speed" : 1,
1143 "srcIn" : 81.01694915254235,
1144 "start" : 103.59322033898304,
1145 "track" : "v0"
1146 },
1147 {
1148 "duration" : 13.220338983050851,
1149 "fadeIn" : 0,
1150 "fadeOut" : 0,
1151 "id" : "D10CE445-F3A3-46E4-B38D-B77AAB60F5FB",
1152 "kind" : "audio",
1153 "linkId" : "17FC9151-B113-420C-8754-353E10218637",
1154 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
1155 "muted" : true,
1156 "newShot" : false,
1157 "speed" : 1,
1158 "srcIn" : 80.93307761120128,
1159 "start" : 103.59322033898304,
1160 "track" : "v1"
1161 },
1162 {
1163 "duration" : 13.220338983050851,
1164 "fadeIn" : 0,
1165 "fadeOut" : 0,
1166 "id" : "4F4DB6E2-2CC1-4696-8CE6-9B7119E8F034",
1167 "kind" : "audio",
1168 "linkId" : "17FC9151-B113-420C-8754-353E10218637",
1169 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
1170 "muted" : false,
1171 "newShot" : false,
1172 "speed" : 1,
1173 "srcIn" : 80.97975391443221,
1174 "start" : 103.59322033898304,
1175 "track" : "v2"
1176 },
1177 {
1178 "duration" : 13.220338983050851,
1179 "fadeIn" : 0,
1180 "fadeOut" : 0,
1181 "id" : "27A9D3E7-68B7-40B3-B332-244F8C0C1BF6",
1182 "kind" : "video",
1183 "linkId" : "17FC9151-B113-420C-8754-353E10218637",
1184 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
1185 "muted" : false,
1186 "newShot" : false,
1187 "speed" : 1,
1188 "srcIn" : 80.84314677820409,
1189 "start" : 103.59322033898304,
1190 "track" : "v3"
1191 },
1192 {
1193 "duration" : 13.220338983050851,
1194 "fadeIn" : 0,
1195 "fadeOut" : 0,
1196 "id" : "5DB078BA-6259-4E90-9C12-E9642D920CED",
1197 "kind" : "video",
1198 "linkId" : "17FC9151-B113-420C-8754-353E10218637",
1199 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
1200 "muted" : false,
1201 "newShot" : false,
1202 "speed" : 1,
1203 "srcIn" : 80.84314219420527,
1204 "start" : 103.59322033898304,
1205 "track" : "v4"
1206 },
1207 {
1208 "duration" : 2.6779661016949063,
1209 "fadeIn" : 0,
1210 "fadeOut" : 0,
1211 "id" : "5FE10E54-0DF5-4AE4-BD39-77E27BADC0B3",
1212 "kind" : "video",
1213 "linkId" : "557C472E-4AC3-4428-8BD4-7817D0701CAB",
1214 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
1215 "muted" : false,
1216 "newShot" : false,
1217 "speed" : 1,
1218 "srcIn" : 22.40677966101694,
1219 "start" : 95.55932203389831,
1220 "track" : "v0"
1221 },
1222 {
1223 "duration" : 2.6779661016949063,
1224 "fadeIn" : 0,
1225 "fadeOut" : 0,
1226 "id" : "981061A0-98AF-4B09-9FEF-6DE942F56A18",
1227 "kind" : "audio",
1228 "linkId" : "557C472E-4AC3-4428-8BD4-7817D0701CAB",
1229 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
1230 "muted" : true,
1231 "newShot" : false,
1232 "speed" : 1,
1233 "srcIn" : 22.322908119675873,
1234 "start" : 95.55932203389831,
1235 "track" : "v1"
1236 },
1237 {
1238 "duration" : 2.6779661016949063,
1239 "fadeIn" : 0,
1240 "fadeOut" : 0,
1241 "id" : "AD29D0F7-2863-42EA-BF7F-AB91267B34D1",
1242 "kind" : "audio",
1243 "linkId" : "557C472E-4AC3-4428-8BD4-7817D0701CAB",
1244 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
1245 "muted" : false,
1246 "newShot" : false,
1247 "speed" : 1,
1248 "srcIn" : 22.369584422906797,
1249 "start" : 95.55932203389831,
1250 "track" : "v2"
1251 },
1252 {
1253 "duration" : 2.6779661016949063,
1254 "fadeIn" : 0,
1255 "fadeOut" : 0,
1256 "id" : "665388F4-40AF-450A-9C85-EE082E1EE31F",
1257 "kind" : "video",
1258 "linkId" : "557C472E-4AC3-4428-8BD4-7817D0701CAB",
1259 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
1260 "muted" : false,
1261 "newShot" : false,
1262 "speed" : 1,
1263 "srcIn" : 22.232977286678675,
1264 "start" : 95.55932203389831,
1265 "track" : "v3"
1266 },
1267 {
1268 "duration" : 2.6779661016949063,
1269 "fadeIn" : 0,
1270 "fadeOut" : 0,
1271 "id" : "3A784722-971F-44D4-B835-69C262B69DC7",
1272 "kind" : "video",
1273 "linkId" : "557C472E-4AC3-4428-8BD4-7817D0701CAB",
1274 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
1275 "muted" : false,
1276 "newShot" : false,
1277 "speed" : 1,
1278 "srcIn" : 22.23297270267986,
1279 "start" : 95.55932203389831,
1280 "track" : "v4"
1281 },
1282 {
1283 "duration" : 5.355932203389827,
1284 "fadeIn" : 0,
1285 "fadeOut" : 0,
1286 "id" : "3101D12D-3956-4472-A689-AC25FAF6B2B3",
1287 "kind" : "video",
1288 "linkId" : "DD4C0E3A-C905-48F3-BA08-017781831163",
1289 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
1290 "muted" : false,
1291 "newShot" : false,
1292 "speed" : 1,
1293 "srcIn" : 29.423728813559308,
1294 "start" : 98.23728813559322,
1295 "track" : "v0"
1296 },
1297 {
1298 "duration" : 5.355932203389827,
1299 "fadeIn" : 0,
1300 "fadeOut" : 0,
1301 "id" : "8AE89603-E830-41CB-98AA-FCE4DDB65200",
1302 "kind" : "audio",
1303 "linkId" : "DD4C0E3A-C905-48F3-BA08-017781831163",
1304 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
1305 "muted" : true,
1306 "newShot" : false,
1307 "speed" : 1,
1308 "srcIn" : 29.33985727221824,
1309 "start" : 98.23728813559322,
1310 "track" : "v1"
1311 },
1312 {
1313 "duration" : 5.355932203389827,
1314 "fadeIn" : 0,
1315 "fadeOut" : 0,
1316 "id" : "30FEEB7F-94C6-4D85-98F3-9D56EFDEA6DB",
1317 "kind" : "audio",
1318 "linkId" : "DD4C0E3A-C905-48F3-BA08-017781831163",
1319 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
1320 "muted" : false,
1321 "newShot" : false,
1322 "speed" : 1,
1323 "srcIn" : 29.386533575449164,
1324 "start" : 98.23728813559322,
1325 "track" : "v2"
1326 },
1327 {
1328 "duration" : 5.355932203389827,
1329 "fadeIn" : 0,
1330 "fadeOut" : 0,
1331 "id" : "986FD34B-99B0-48E4-8AA8-EA9747E0F300",
1332 "kind" : "video",
1333 "linkId" : "DD4C0E3A-C905-48F3-BA08-017781831163",
1334 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
1335 "muted" : false,
1336 "newShot" : false,
1337 "speed" : 1,
1338 "srcIn" : 29.249926439221042,
1339 "start" : 98.23728813559322,
1340 "track" : "v3"
1341 },
1342 {
1343 "duration" : 5.355932203389827,
1344 "fadeIn" : 0,
1345 "fadeOut" : 0,
1346 "id" : "847F33A9-CA10-4FA4-95E4-3B250E43146A",
1347 "kind" : "video",
1348 "linkId" : "DD4C0E3A-C905-48F3-BA08-017781831163",
1349 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
1350 "muted" : false,
1351 "newShot" : false,
1352 "speed" : 1,
1353 "srcIn" : 29.249921855222226,
1354 "start" : 98.23728813559322,
1355 "track" : "v4"
1356 },
1357 {
1358 "duration" : 7.423728813559308,
1359 "fadeIn" : 0,
1360 "fadeOut" : 0,
1361 "id" : "11BBE731-6722-4267-9D42-9A793553DE06",
1362 "kind" : "video",
1363 "linkId" : "DA913126-892F-46B7-9A7A-51A2A1951521",
1364 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
1365 "muted" : false,
1366 "newShot" : false,
1367 "speed" : 1,
1368 "srcIn" : 193.55932203389838,
1369 "start" : 135.66101694915255,
1370 "track" : "v0"
1371 },
1372 {
1373 "duration" : 7.423728813559308,
1374 "fadeIn" : 0,
1375 "fadeOut" : 0,
1376 "id" : "22421728-439D-4470-95A1-EC2EF0D21C81",
1377 "kind" : "audio",
1378 "linkId" : "DA913126-892F-46B7-9A7A-51A2A1951521",
1379 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
1380 "muted" : true,
1381 "newShot" : false,
1382 "speed" : 1,
1383 "srcIn" : 193.47545049255731,
1384 "start" : 135.66101694915255,
1385 "track" : "v1"
1386 },
1387 {
1388 "duration" : 7.423728813559308,
1389 "fadeIn" : 0,
1390 "fadeOut" : 0,
1391 "id" : "AE02280F-55B2-4037-9D75-5CC083936FD1",
1392 "kind" : "audio",
1393 "linkId" : "DA913126-892F-46B7-9A7A-51A2A1951521",
1394 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
1395 "muted" : false,
1396 "newShot" : false,
1397 "speed" : 1,
1398 "srcIn" : 193.52212679578824,
1399 "start" : 135.66101694915255,
1400 "track" : "v2"
1401 },
1402 {
1403 "duration" : 7.423728813559308,
1404 "fadeIn" : 0,
1405 "fadeOut" : 0,
1406 "id" : "E2A5436F-D353-42DF-9A48-073C9FE6246F",
1407 "kind" : "video",
1408 "linkId" : "DA913126-892F-46B7-9A7A-51A2A1951521",
1409 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
1410 "muted" : false,
1411 "newShot" : false,
1412 "speed" : 1,
1413 "srcIn" : 193.38551965956012,
1414 "start" : 135.66101694915255,
1415 "track" : "v3"
1416 },
1417 {
1418 "duration" : 7.423728813559308,
1419 "fadeIn" : 0,
1420 "fadeOut" : 0,
1421 "id" : "A1DA8F0A-B592-4DBD-9926-213A33E42749",
1422 "kind" : "video",
1423 "linkId" : "DA913126-892F-46B7-9A7A-51A2A1951521",
1424 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
1425 "muted" : false,
1426 "newShot" : false,
1427 "speed" : 1,
1428 "srcIn" : 193.3855150755613,
1429 "start" : 135.66101694915255,
1430 "track" : "v4"
1431 },
1432 {
1433 "duration" : 2.3389830508474745,
1434 "fadeIn" : 0,
1435 "fadeOut" : 0,
1436 "id" : "0F625D10-4206-4D6D-8623-4F14529073E3",
1437 "kind" : "video",
1438 "linkId" : "E458F570-26D5-47DD-87C2-E11FF928A2CA",
1439 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
1440 "muted" : false,
1441 "newShot" : false,
1442 "speed" : 1,
1443 "srcIn" : 214.9152542372882,
1444 "start" : 143.08474576271186,
1445 "track" : "v0"
1446 },
1447 {
1448 "duration" : 2.3389830508474745,
1449 "fadeIn" : 0,
1450 "fadeOut" : 0,
1451 "id" : "F69F71DF-6EEA-48C1-A1F0-F8EE77EE669C",
1452 "kind" : "audio",
1453 "linkId" : "E458F570-26D5-47DD-87C2-E11FF928A2CA",
1454 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
1455 "muted" : true,
1456 "newShot" : false,
1457 "speed" : 1,
1458 "srcIn" : 214.83138269594713,
1459 "start" : 143.08474576271186,
1460 "track" : "v1"
1461 },
1462 {
1463 "duration" : 2.3389830508474745,
1464 "fadeIn" : 0,
1465 "fadeOut" : 0,
1466 "id" : "63619F12-6728-45D4-9664-77B370607EEB",
1467 "kind" : "audio",
1468 "linkId" : "E458F570-26D5-47DD-87C2-E11FF928A2CA",
1469 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
1470 "muted" : false,
1471 "newShot" : false,
1472 "speed" : 1,
1473 "srcIn" : 214.87805899917805,
1474 "start" : 143.08474576271186,
1475 "track" : "v2"
1476 },
1477 {
1478 "duration" : 2.3389830508474745,
1479 "fadeIn" : 0,
1480 "fadeOut" : 0,
1481 "id" : "4F7963B1-6583-494B-A319-8F2BF093C517",
1482 "kind" : "video",
1483 "linkId" : "E458F570-26D5-47DD-87C2-E11FF928A2CA",
1484 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
1485 "muted" : false,
1486 "newShot" : false,
1487 "speed" : 1,
1488 "srcIn" : 214.74145186294993,
1489 "start" : 143.08474576271186,
1490 "track" : "v3"
1491 },
1492 {
1493 "duration" : 2.3389830508474745,
1494 "fadeIn" : 0,
1495 "fadeOut" : 0,
1496 "id" : "95B72825-D2B7-4D97-8C1F-63E1EB598DF9",
1497 "kind" : "video",
1498 "linkId" : "E458F570-26D5-47DD-87C2-E11FF928A2CA",
1499 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
1500 "muted" : false,
1501 "newShot" : false,
1502 "speed" : 1,
1503 "srcIn" : 214.7414472789511,
1504 "start" : 143.08474576271186,
1505 "track" : "v4"
1506 },
1507 {
1508 "duration" : 6.135593220338961,
1509 "fadeIn" : 0,
1510 "fadeOut" : 0,
1511 "id" : "112CE6B8-BFDF-497C-9C69-CCE7802DB06C",
1512 "kind" : "video",
1513 "linkId" : "A28DCAA3-B061-4E38-8372-878AD7EF2060",
1514 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
1515 "muted" : false,
1516 "newShot" : false,
1517 "speed" : 1,
1518 "srcIn" : 226.33898305084753,
1519 "start" : 145.42372881355934,
1520 "track" : "v0"
1521 },
1522 {
1523 "duration" : 6.135593220338961,
1524 "fadeIn" : 0,
1525 "fadeOut" : 0,
1526 "id" : "4040BC36-A9F4-4ED3-A653-1978DDDAF7C5",
1527 "kind" : "audio",
1528 "linkId" : "A28DCAA3-B061-4E38-8372-878AD7EF2060",
1529 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
1530 "muted" : true,
1531 "newShot" : false,
1532 "speed" : 1,
1533 "srcIn" : 226.25511150950646,
1534 "start" : 145.42372881355934,
1535 "track" : "v1"
1536 },
1537 {
1538 "duration" : 6.135593220338961,
1539 "fadeIn" : 0,
1540 "fadeOut" : 0,
1541 "id" : "EFD116A1-EB5D-4CE3-B95D-14B73ECDE548",
1542 "kind" : "audio",
1543 "linkId" : "A28DCAA3-B061-4E38-8372-878AD7EF2060",
1544 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
1545 "muted" : false,
1546 "newShot" : false,
1547 "speed" : 1,
1548 "srcIn" : 226.3017878127374,
1549 "start" : 145.42372881355934,
1550 "track" : "v2"
1551 },
1552 {
1553 "duration" : 6.135593220338961,
1554 "fadeIn" : 0,
1555 "fadeOut" : 0,
1556 "id" : "0AE31757-C7B4-49BB-AB63-0641A6D83BC3",
1557 "kind" : "video",
1558 "linkId" : "A28DCAA3-B061-4E38-8372-878AD7EF2060",
1559 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
1560 "muted" : false,
1561 "newShot" : false,
1562 "speed" : 1,
1563 "srcIn" : 226.16518067650927,
1564 "start" : 145.42372881355934,
1565 "track" : "v3"
1566 },
1567 {
1568 "duration" : 6.135593220338961,
1569 "fadeIn" : 0,
1570 "fadeOut" : 0,
1571 "id" : "9E1DFB7E-F165-4D38-AC11-812E55244554",
1572 "kind" : "video",
1573 "linkId" : "A28DCAA3-B061-4E38-8372-878AD7EF2060",
1574 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
1575 "muted" : false,
1576 "newShot" : false,
1577 "speed" : 1,
1578 "srcIn" : 226.16517609251045,
1579 "start" : 145.42372881355934,
1580 "track" : "v4"
1581 },
1582 {
1583 "duration" : 8.508474576271198,
1584 "fadeIn" : 0,
1585 "fadeOut" : 0,
1586 "id" : "6C78F572-76AD-4191-822E-E127F9D580E4",
1587 "kind" : "video",
1588 "linkId" : "CBF9981A-DB5A-459B-96C0-CFDCEB644C78",
1589 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
1590 "muted" : false,
1591 "newShot" : false,
1592 "speed" : 1,
1593 "srcIn" : 233.8305084745763,
1594 "start" : 151.5593220338983,
1595 "track" : "v0"
1596 },
1597 {
1598 "duration" : 8.508474576271198,
1599 "fadeIn" : 0,
1600 "fadeOut" : 0,
1601 "id" : "443C9738-2CD2-45FF-89ED-93D209FFBFCA",
1602 "kind" : "audio",
1603 "linkId" : "CBF9981A-DB5A-459B-96C0-CFDCEB644C78",
1604 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
1605 "muted" : true,
1606 "newShot" : false,
1607 "speed" : 1,
1608 "srcIn" : 233.74663693323524,
1609 "start" : 151.5593220338983,
1610 "track" : "v1"
1611 },
1612 {
1613 "duration" : 8.508474576271198,
1614 "fadeIn" : 0,
1615 "fadeOut" : 0,
1616 "id" : "B28457F9-9054-461C-99E1-73E68152E679",
1617 "kind" : "audio",
1618 "linkId" : "CBF9981A-DB5A-459B-96C0-CFDCEB644C78",
1619 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
1620 "muted" : false,
1621 "newShot" : false,
1622 "speed" : 1,
1623 "srcIn" : 233.79331323646616,
1624 "start" : 151.5593220338983,
1625 "track" : "v2"
1626 },
1627 {
1628 "duration" : 8.508474576271198,
1629 "fadeIn" : 0,
1630 "fadeOut" : 0,
1631 "id" : "3D057A10-3381-4125-B50C-BC1998039A9D",
1632 "kind" : "video",
1633 "linkId" : "CBF9981A-DB5A-459B-96C0-CFDCEB644C78",
1634 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
1635 "muted" : false,
1636 "newShot" : false,
1637 "speed" : 1,
1638 "srcIn" : 233.65670610023804,
1639 "start" : 151.5593220338983,
1640 "track" : "v3"
1641 },
1642 {
1643 "duration" : 8.508474576271198,
1644 "fadeIn" : 0,
1645 "fadeOut" : 0,
1646 "id" : "BDECC22C-1053-4191-B2F2-2EDBC4F3B5B5",
1647 "kind" : "video",
1648 "linkId" : "CBF9981A-DB5A-459B-96C0-CFDCEB644C78",
1649 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
1650 "muted" : false,
1651 "newShot" : false,
1652 "speed" : 1,
1653 "srcIn" : 233.65670151623922,
1654 "start" : 151.5593220338983,
1655 "track" : "v4"
1656 },
1657 {
1658 "duration" : 9.050847457627128,
1659 "fadeIn" : 0,
1660 "fadeOut" : 0,
1661 "id" : "62014848-FAC6-4BBC-8E7A-14F0B28CC0E6",
1662 "kind" : "video",
1663 "linkId" : "4288CB77-E89F-42B1-927E-DEADACE56BA4",
1664 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
1665 "muted" : false,
1666 "newShot" : false,
1667 "speed" : 1,
1668 "srcIn" : 279.6271186440679,
1669 "start" : 160.0677966101695,
1670 "track" : "v0"
1671 },
1672 {
1673 "duration" : 9.050847457627128,
1674 "fadeIn" : 0,
1675 "fadeOut" : 0,
1676 "id" : "59D15847-5750-41D0-84C8-63D08E771477",
1677 "kind" : "audio",
1678 "linkId" : "4288CB77-E89F-42B1-927E-DEADACE56BA4",
1679 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
1680 "muted" : true,
1681 "newShot" : false,
1682 "speed" : 1,
1683 "srcIn" : 279.5432471027268,
1684 "start" : 160.0677966101695,
1685 "track" : "v1"
1686 },
1687 {
1688 "duration" : 9.050847457627128,
1689 "fadeIn" : 0,
1690 "fadeOut" : 0,
1691 "id" : "3404389F-7945-4513-A69B-1C428F1D90BE",
1692 "kind" : "audio",
1693 "linkId" : "4288CB77-E89F-42B1-927E-DEADACE56BA4",
1694 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
1695 "muted" : false,
1696 "newShot" : false,
1697 "speed" : 1,
1698 "srcIn" : 279.58992340595773,
1699 "start" : 160.0677966101695,
1700 "track" : "v2"
1701 },
1702 {
1703 "duration" : 9.050847457627128,
1704 "fadeIn" : 0,
1705 "fadeOut" : 0,
1706 "id" : "91B4872C-9861-4274-ADDC-E13CB1843662",
1707 "kind" : "video",
1708 "linkId" : "4288CB77-E89F-42B1-927E-DEADACE56BA4",
1709 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
1710 "muted" : false,
1711 "newShot" : false,
1712 "speed" : 1,
1713 "srcIn" : 279.4533162697296,
1714 "start" : 160.0677966101695,
1715 "track" : "v3"
1716 },
1717 {
1718 "duration" : 9.050847457627128,
1719 "fadeIn" : 0,
1720 "fadeOut" : 0,
1721 "id" : "BA59E5A7-F140-4986-8E5B-E8994D035D5D",
1722 "kind" : "video",
1723 "linkId" : "4288CB77-E89F-42B1-927E-DEADACE56BA4",
1724 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
1725 "muted" : false,
1726 "newShot" : false,
1727 "speed" : 1,
1728 "srcIn" : 279.4533116857308,
1729 "start" : 160.0677966101695,
1730 "track" : "v4"
1731 },
1732 {
1733 "duration" : 20.983050847457605,
1734 "fadeIn" : 0,
1735 "fadeOut" : 0,
1736 "id" : "D9CE7BC0-0A5C-4C90-A15A-7694E9E749B7",
1737 "kind" : "video",
1738 "linkId" : "1B8AA32F-8AAC-4699-91CD-75C5E92F7BA0",
1739 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
1740 "muted" : false,
1741 "newShot" : false,
1742 "speed" : 1,
1743 "srcIn" : 341.42372881355936,
1744 "start" : 169.11864406779662,
1745 "track" : "v0"
1746 },
1747 {
1748 "duration" : 20.983050847457605,
1749 "fadeIn" : 0,
1750 "fadeOut" : 0,
1751 "id" : "6F77C3F0-E5FF-426F-99E1-AA28B988BB0A",
1752 "kind" : "audio",
1753 "linkId" : "1B8AA32F-8AAC-4699-91CD-75C5E92F7BA0",
1754 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
1755 "muted" : true,
1756 "newShot" : false,
1757 "speed" : 1,
1758 "srcIn" : 341.3398572722183,
1759 "start" : 169.11864406779662,
1760 "track" : "v1"
1761 },
1762 {
1763 "duration" : 20.983050847457605,
1764 "fadeIn" : 0,
1765 "fadeOut" : 0,
1766 "id" : "DAF8FD62-8F36-4F73-94EE-5380D1E30401",
1767 "kind" : "audio",
1768 "linkId" : "1B8AA32F-8AAC-4699-91CD-75C5E92F7BA0",
1769 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
1770 "muted" : false,
1771 "newShot" : false,
1772 "speed" : 1,
1773 "srcIn" : 341.3865335754492,
1774 "start" : 169.11864406779662,
1775 "track" : "v2"
1776 },
1777 {
1778 "duration" : 20.983050847457605,
1779 "fadeIn" : 0,
1780 "fadeOut" : 0,
1781 "id" : "74FC2FC5-A8A4-469B-9482-73979D2C373D",
1782 "kind" : "video",
1783 "linkId" : "1B8AA32F-8AAC-4699-91CD-75C5E92F7BA0",
1784 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
1785 "muted" : false,
1786 "newShot" : false,
1787 "speed" : 1,
1788 "srcIn" : 341.2499264392211,
1789 "start" : 169.11864406779662,
1790 "track" : "v3"
1791 },
1792 {
1793 "duration" : 20.983050847457605,
1794 "fadeIn" : 0,
1795 "fadeOut" : 0,
1796 "id" : "FFA5E7DE-E5C3-4ED5-9004-3201CD4A30DA",
1797 "kind" : "video",
1798 "linkId" : "1B8AA32F-8AAC-4699-91CD-75C5E92F7BA0",
1799 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
1800 "muted" : false,
1801 "newShot" : false,
1802 "speed" : 1,
1803 "srcIn" : 341.2499218552223,
1804 "start" : 169.11864406779662,
1805 "track" : "v4"
1806 },
1807 {
1808 "duration" : 3.1525423728813564,
1809 "fadeIn" : 0,
1810 "fadeOut" : 0,
1811 "id" : "F7F6AF02-0DE4-4485-8590-753751DA656D",
1812 "kind" : "video",
1813 "linkId" : "1B2E5F19-8476-4AC4-B136-E5222F541298",
1814 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
1815 "muted" : false,
1816 "newShot" : false,
1817 "speed" : 1,
1818 "srcIn" : 516.9830508474577,
1819 "start" : 190.10169491525423,
1820 "track" : "v0"
1821 },
1822 {
1823 "duration" : 3.1525423728813564,
1824 "fadeIn" : 0,
1825 "fadeOut" : 0,
1826 "id" : "0F29059A-7B58-4133-AA5B-F26BDD11FCEA",
1827 "kind" : "audio",
1828 "linkId" : "1B2E5F19-8476-4AC4-B136-E5222F541298",
1829 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
1830 "muted" : true,
1831 "newShot" : false,
1832 "speed" : 1,
1833 "srcIn" : 516.8991793061166,
1834 "start" : 190.10169491525423,
1835 "track" : "v1"
1836 },
1837 {
1838 "duration" : 3.1525423728813564,
1839 "fadeIn" : 0,
1840 "fadeOut" : 0,
1841 "id" : "67E73C14-2908-4A77-83E5-6A8F38F25BF4",
1842 "kind" : "audio",
1843 "linkId" : "1B2E5F19-8476-4AC4-B136-E5222F541298",
1844 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
1845 "muted" : false,
1846 "newShot" : false,
1847 "speed" : 1,
1848 "srcIn" : 516.9458556093475,
1849 "start" : 190.10169491525423,
1850 "track" : "v2"
1851 },
1852 {
1853 "duration" : 3.1525423728813564,
1854 "fadeIn" : 0,
1855 "fadeOut" : 0,
1856 "id" : "DACD3C89-6F61-4E5A-BFF4-ABCB5013028B",
1857 "kind" : "video",
1858 "linkId" : "1B2E5F19-8476-4AC4-B136-E5222F541298",
1859 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
1860 "muted" : false,
1861 "newShot" : false,
1862 "speed" : 1,
1863 "srcIn" : 516.8092484731194,
1864 "start" : 190.10169491525423,
1865 "track" : "v3"
1866 },
1867 {
1868 "duration" : 3.1525423728813564,
1869 "fadeIn" : 0,
1870 "fadeOut" : 0,
1871 "id" : "D85A8D17-5419-4FE8-A767-7886ACAD06D2",
1872 "kind" : "video",
1873 "linkId" : "1B2E5F19-8476-4AC4-B136-E5222F541298",
1874 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
1875 "muted" : false,
1876 "newShot" : false,
1877 "speed" : 1,
1878 "srcIn" : 516.8092438891206,
1879 "start" : 190.10169491525423,
1880 "track" : "v4"
1881 },
1882 {
1883 "duration" : 3.0508474576271283,
1884 "fadeIn" : 0,
1885 "fadeOut" : 0,
1886 "id" : "22E40845-33D9-4113-9E6A-72ABDBAF556B",
1887 "kind" : "video",
1888 "linkId" : "3CC5F7F4-7A10-4825-BA22-58F0E5FA39EC",
1889 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
1890 "muted" : false,
1891 "newShot" : false,
1892 "speed" : 1,
1893 "srcIn" : 526.0677966101696,
1894 "start" : 193.25423728813558,
1895 "track" : "v0"
1896 },
1897 {
1898 "duration" : 3.0508474576271283,
1899 "fadeIn" : 0,
1900 "fadeOut" : 0,
1901 "id" : "80104D35-A942-4197-BE85-9E999B164BD5",
1902 "kind" : "audio",
1903 "linkId" : "3CC5F7F4-7A10-4825-BA22-58F0E5FA39EC",
1904 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
1905 "muted" : true,
1906 "newShot" : false,
1907 "speed" : 1,
1908 "srcIn" : 525.9839250688285,
1909 "start" : 193.25423728813558,
1910 "track" : "v1"
1911 },
1912 {
1913 "duration" : 3.0508474576271283,
1914 "fadeIn" : 0,
1915 "fadeOut" : 0,
1916 "id" : "98A4488E-8AFB-4DAF-AA4A-B6171B5BE4B0",
1917 "kind" : "audio",
1918 "linkId" : "3CC5F7F4-7A10-4825-BA22-58F0E5FA39EC",
1919 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
1920 "muted" : false,
1921 "newShot" : false,
1922 "speed" : 1,
1923 "srcIn" : 526.0306013720594,
1924 "start" : 193.25423728813558,
1925 "track" : "v2"
1926 },
1927 {
1928 "duration" : 3.0508474576271283,
1929 "fadeIn" : 0,
1930 "fadeOut" : 0,
1931 "id" : "31F9AFDE-F19F-4D52-8468-6E4FD6E73843",
1932 "kind" : "video",
1933 "linkId" : "3CC5F7F4-7A10-4825-BA22-58F0E5FA39EC",
1934 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
1935 "muted" : false,
1936 "newShot" : false,
1937 "speed" : 1,
1938 "srcIn" : 525.8939942358313,
1939 "start" : 193.25423728813558,
1940 "track" : "v3"
1941 },
1942 {
1943 "duration" : 3.0508474576271283,
1944 "fadeIn" : 0,
1945 "fadeOut" : 0,
1946 "id" : "58C86E52-F741-4EAF-93E1-9AD9B238BEBC",
1947 "kind" : "video",
1948 "linkId" : "3CC5F7F4-7A10-4825-BA22-58F0E5FA39EC",
1949 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
1950 "muted" : false,
1951 "newShot" : false,
1952 "speed" : 1,
1953 "srcIn" : 525.8939896518325,
1954 "start" : 193.25423728813558,
1955 "track" : "v4"
1956 },
1957 {
1958 "duration" : 12.101694915254257,
1959 "fadeIn" : 0,
1960 "fadeOut" : 0,
1961 "id" : "729D86A6-9A2B-4F18-97A2-9E4B8842705A",
1962 "kind" : "video",
1963 "linkId" : "96ED86B5-C0C4-427A-AB87-51061D1B6C02",
1964 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
1965 "muted" : false,
1966 "newShot" : false,
1967 "speed" : 1,
1968 "srcIn" : 547.3898305084747,
1969 "start" : 196.3050847457627,
1970 "track" : "v0"
1971 },
1972 {
1973 "duration" : 12.101694915254257,
1974 "fadeIn" : 0,
1975 "fadeOut" : 0,
1976 "id" : "537BA6C3-FE42-41F7-AFD1-CCD1498FD164",
1977 "kind" : "audio",
1978 "linkId" : "96ED86B5-C0C4-427A-AB87-51061D1B6C02",
1979 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
1980 "muted" : true,
1981 "newShot" : false,
1982 "speed" : 1,
1983 "srcIn" : 547.3059589671336,
1984 "start" : 196.3050847457627,
1985 "track" : "v1"
1986 },
1987 {
1988 "duration" : 12.101694915254257,
1989 "fadeIn" : 0,
1990 "fadeOut" : 0,
1991 "id" : "DF50C8A8-7061-47D8-9E3E-F76059F64F17",
1992 "kind" : "audio",
1993 "linkId" : "96ED86B5-C0C4-427A-AB87-51061D1B6C02",
1994 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
1995 "muted" : false,
1996 "newShot" : false,
1997 "speed" : 1,
1998 "srcIn" : 547.3526352703645,
1999 "start" : 196.3050847457627,
2000 "track" : "v2"
2001 },
2002 {
2003 "duration" : 12.101694915254257,
2004 "fadeIn" : 0,
2005 "fadeOut" : 0,
2006 "id" : "396149DF-84B8-447F-8A4F-6BC523F5F5C2",
2007 "kind" : "video",
2008 "linkId" : "96ED86B5-C0C4-427A-AB87-51061D1B6C02",
2009 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
2010 "muted" : false,
2011 "newShot" : false,
2012 "speed" : 1,
2013 "srcIn" : 547.2160281341364,
2014 "start" : 196.3050847457627,
2015 "track" : "v3"
2016 },
2017 {
2018 "duration" : 12.101694915254257,
2019 "fadeIn" : 0,
2020 "fadeOut" : 0,
2021 "id" : "8BAC1614-CB12-4DC2-9F56-99B2DD91B028",
2022 "kind" : "video",
2023 "linkId" : "96ED86B5-C0C4-427A-AB87-51061D1B6C02",
2024 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
2025 "muted" : false,
2026 "newShot" : false,
2027 "speed" : 1,
2028 "srcIn" : 547.2160235501376,
2029 "start" : 196.3050847457627,
2030 "track" : "v4"
2031 },
2032 {
2033 "duration" : 4.203389830508456,
2034 "fadeIn" : 0,
2035 "fadeOut" : 0,
2036 "id" : "C677D610-A145-44E9-A5DF-4F24EF105083",
2037 "kind" : "video",
2038 "linkId" : "61201D64-66C9-4C57-8602-97A96701E0BF",
2039 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
2040 "muted" : false,
2041 "newShot" : false,
2042 "speed" : 1,
2043 "srcIn" : 566.3389830508476,
2044 "start" : 208.40677966101697,
2045 "track" : "v0"
2046 },
2047 {
2048 "duration" : 4.203389830508456,
2049 "fadeIn" : 0,
2050 "fadeOut" : 0,
2051 "id" : "2D9D8A03-3523-4FF8-A897-637F158C3C79",
2052 "kind" : "audio",
2053 "linkId" : "61201D64-66C9-4C57-8602-97A96701E0BF",
2054 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
2055 "muted" : true,
2056 "newShot" : false,
2057 "speed" : 1,
2058 "srcIn" : 566.2551115095065,
2059 "start" : 208.40677966101697,
2060 "track" : "v1"
2061 },
2062 {
2063 "duration" : 4.203389830508456,
2064 "fadeIn" : 0,
2065 "fadeOut" : 0,
2066 "id" : "61480E1E-954B-45A2-9C3A-FF0F0BB690FC",
2067 "kind" : "audio",
2068 "linkId" : "61201D64-66C9-4C57-8602-97A96701E0BF",
2069 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
2070 "muted" : false,
2071 "newShot" : false,
2072 "speed" : 1,
2073 "srcIn" : 566.3017878127374,
2074 "start" : 208.40677966101697,
2075 "track" : "v2"
2076 },
2077 {
2078 "duration" : 4.203389830508456,
2079 "fadeIn" : 0,
2080 "fadeOut" : 0,
2081 "id" : "0501FE38-6C08-467C-9E1C-E41CA0498523",
2082 "kind" : "video",
2083 "linkId" : "61201D64-66C9-4C57-8602-97A96701E0BF",
2084 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
2085 "muted" : false,
2086 "newShot" : false,
2087 "speed" : 1,
2088 "srcIn" : 566.1651806765093,
2089 "start" : 208.40677966101697,
2090 "track" : "v3"
2091 },
2092 {
2093 "duration" : 4.203389830508456,
2094 "fadeIn" : 0,
2095 "fadeOut" : 0,
2096 "id" : "74D8A914-BC66-4C84-BFFA-A0CA6639D704",
2097 "kind" : "video",
2098 "linkId" : "61201D64-66C9-4C57-8602-97A96701E0BF",
2099 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
2100 "muted" : false,
2101 "newShot" : false,
2102 "speed" : 1,
2103 "srcIn" : 566.1651760925105,
2104 "start" : 208.40677966101697,
2105 "track" : "v4"
2106 },
2107 {
2108 "duration" : 6.406779661016941,
2109 "fadeIn" : 0,
2110 "fadeOut" : 0,
2111 "id" : "9B498BC6-78AB-44F5-B23B-25E8F7DDC89B",
2112 "kind" : "video",
2113 "linkId" : "02AB076D-1732-49F9-AA10-410FA9A3E78F",
2114 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
2115 "muted" : false,
2116 "newShot" : false,
2117 "speed" : 1,
2118 "srcIn" : 571.35593220339,
2119 "start" : 212.61016949152543,
2120 "track" : "v0"
2121 },
2122 {
2123 "duration" : 6.406779661016941,
2124 "fadeIn" : 0,
2125 "fadeOut" : 0,
2126 "id" : "8C7D77E2-3FA1-41F1-884C-63C516C4AE33",
2127 "kind" : "audio",
2128 "linkId" : "02AB076D-1732-49F9-AA10-410FA9A3E78F",
2129 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
2130 "muted" : true,
2131 "newShot" : false,
2132 "speed" : 1,
2133 "srcIn" : 571.272060662049,
2134 "start" : 212.61016949152543,
2135 "track" : "v1"
2136 },
2137 {
2138 "duration" : 6.406779661016941,
2139 "fadeIn" : 0,
2140 "fadeOut" : 0,
2141 "id" : "21A1243A-B2AF-4637-B9E4-3BA86980E892",
2142 "kind" : "audio",
2143 "linkId" : "02AB076D-1732-49F9-AA10-410FA9A3E78F",
2144 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
2145 "muted" : false,
2146 "newShot" : false,
2147 "speed" : 1,
2148 "srcIn" : 571.3187369652799,
2149 "start" : 212.61016949152543,
2150 "track" : "v2"
2151 },
2152 {
2153 "duration" : 6.406779661016941,
2154 "fadeIn" : 0,
2155 "fadeOut" : 0,
2156 "id" : "F361620E-6686-4423-BFBD-736FF93CDEC5",
2157 "kind" : "video",
2158 "linkId" : "02AB076D-1732-49F9-AA10-410FA9A3E78F",
2159 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
2160 "muted" : false,
2161 "newShot" : false,
2162 "speed" : 1,
2163 "srcIn" : 571.1821298290517,
2164 "start" : 212.61016949152543,
2165 "track" : "v3"
2166 },
2167 {
2168 "duration" : 6.406779661016941,
2169 "fadeIn" : 0,
2170 "fadeOut" : 0,
2171 "id" : "2A763F6C-BEF9-435E-8F42-C72F8F08381A",
2172 "kind" : "video",
2173 "linkId" : "02AB076D-1732-49F9-AA10-410FA9A3E78F",
2174 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
2175 "muted" : false,
2176 "newShot" : false,
2177 "speed" : 1,
2178 "srcIn" : 571.1821252450529,
2179 "start" : 212.61016949152543,
2180 "track" : "v4"
2181 },
2182 {
2183 "duration" : 4.610169491525454,
2184 "fadeIn" : 0,
2185 "fadeOut" : 0,
2186 "id" : "ADAD35C0-6E8C-4D37-B4E4-D2859B73BE27",
2187 "kind" : "video",
2188 "linkId" : "3473CFA9-BD18-42DA-AD05-6C5914B914B6",
2189 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
2190 "muted" : false,
2191 "newShot" : false,
2192 "speed" : 1,
2193 "srcIn" : 584.813559322034,
2194 "start" : 222.06779661016947,
2195 "track" : "v0"
2196 },
2197 {
2198 "duration" : 4.610169491525454,
2199 "fadeIn" : 0,
2200 "fadeOut" : 0,
2201 "id" : "86415894-FDC6-4F60-A9B8-0954DBA0D01D",
2202 "kind" : "audio",
2203 "linkId" : "3473CFA9-BD18-42DA-AD05-6C5914B914B6",
2204 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
2205 "muted" : true,
2206 "newShot" : false,
2207 "speed" : 1,
2208 "srcIn" : 584.729687780693,
2209 "start" : 222.06779661016947,
2210 "track" : "v1"
2211 },
2212 {
2213 "duration" : 4.610169491525454,
2214 "fadeIn" : 0,
2215 "fadeOut" : 0,
2216 "id" : "48CD7335-7777-4C44-A35A-17CFAA490549",
2217 "kind" : "audio",
2218 "linkId" : "3473CFA9-BD18-42DA-AD05-6C5914B914B6",
2219 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
2220 "muted" : false,
2221 "newShot" : false,
2222 "speed" : 1,
2223 "srcIn" : 584.7763640839239,
2224 "start" : 222.06779661016947,
2225 "track" : "v2"
2226 },
2227 {
2228 "duration" : 4.610169491525454,
2229 "fadeIn" : 0,
2230 "fadeOut" : 0,
2231 "id" : "54BD8C49-005E-4485-9DC1-7F916CF8BDD3",
2232 "kind" : "video",
2233 "linkId" : "3473CFA9-BD18-42DA-AD05-6C5914B914B6",
2234 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
2235 "muted" : false,
2236 "newShot" : false,
2237 "speed" : 1,
2238 "srcIn" : 584.6397569476958,
2239 "start" : 222.06779661016947,
2240 "track" : "v3"
2241 },
2242 {
2243 "duration" : 4.610169491525454,
2244 "fadeIn" : 0,
2245 "fadeOut" : 0,
2246 "id" : "23AEC066-77D9-4BA3-8117-EE7A8161CBBD",
2247 "kind" : "video",
2248 "linkId" : "3473CFA9-BD18-42DA-AD05-6C5914B914B6",
2249 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
2250 "muted" : false,
2251 "newShot" : false,
2252 "speed" : 1,
2253 "srcIn" : 584.639752363697,
2254 "start" : 222.06779661016947,
2255 "track" : "v4"
2256 },
2257 {
2258 "duration" : 2.237288135593218,
2259 "fadeIn" : 0,
2260 "fadeOut" : 0,
2261 "id" : "0991B547-CD7C-4CC3-87EC-AD16FF79CEEC",
2262 "kind" : "video",
2263 "linkId" : "02A86E0B-9E9F-4742-A7B2-44024A6F3E6B",
2264 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
2265 "muted" : false,
2266 "newShot" : false,
2267 "speed" : 1,
2268 "srcIn" : 577.7627118644069,
2269 "start" : 219.01694915254237,
2270 "track" : "v0"
2271 },
2272 {
2273 "duration" : 2.237288135593218,
2274 "fadeIn" : 0,
2275 "fadeOut" : 0,
2276 "id" : "A7485496-22C3-4CB6-8034-93F5ACE90052",
2277 "kind" : "audio",
2278 "linkId" : "02A86E0B-9E9F-4742-A7B2-44024A6F3E6B",
2279 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
2280 "muted" : true,
2281 "newShot" : false,
2282 "speed" : 1,
2283 "srcIn" : 577.6788403230659,
2284 "start" : 219.01694915254237,
2285 "track" : "v1"
2286 },
2287 {
2288 "duration" : 2.237288135593218,
2289 "fadeIn" : 0,
2290 "fadeOut" : 0,
2291 "id" : "A8426C1F-0305-45DF-A0C4-57CB7AD56360",
2292 "kind" : "audio",
2293 "linkId" : "02A86E0B-9E9F-4742-A7B2-44024A6F3E6B",
2294 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
2295 "muted" : false,
2296 "newShot" : false,
2297 "speed" : 1,
2298 "srcIn" : 577.7255166262968,
2299 "start" : 219.01694915254237,
2300 "track" : "v2"
2301 },
2302 {
2303 "duration" : 2.237288135593218,
2304 "fadeIn" : 0,
2305 "fadeOut" : 0,
2306 "id" : "F0053E8C-8FC8-47E2-8431-73026DBACEC8",
2307 "kind" : "video",
2308 "linkId" : "02A86E0B-9E9F-4742-A7B2-44024A6F3E6B",
2309 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
2310 "muted" : false,
2311 "newShot" : false,
2312 "speed" : 1,
2313 "srcIn" : 577.5889094900687,
2314 "start" : 219.01694915254237,
2315 "track" : "v3"
2316 },
2317 {
2318 "duration" : 2.237288135593218,
2319 "fadeIn" : 0,
2320 "fadeOut" : 0,
2321 "id" : "EF04E82D-AA3C-41CA-B856-4A6914526EC1",
2322 "kind" : "video",
2323 "linkId" : "02A86E0B-9E9F-4742-A7B2-44024A6F3E6B",
2324 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
2325 "muted" : false,
2326 "newShot" : false,
2327 "speed" : 1,
2328 "srcIn" : 577.5889049060698,
2329 "start" : 219.01694915254237,
2330 "track" : "v4"
2331 },
2332 {
2333 "duration" : 0.33898305084744607,
2334 "fadeIn" : 0,
2335 "fadeOut" : 0,
2336 "id" : "CABADC3E-FA59-4C65-87F2-13BFBE96E557",
2337 "kind" : "video",
2338 "linkId" : "00C998B7-CF37-4D26-B09F-6C36B9B32D82",
2339 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
2340 "muted" : false,
2341 "newShot" : false,
2342 "speed" : 1,
2343 "srcIn" : 582.4067796610171,
2344 "start" : 221.25423728813558,
2345 "track" : "v0"
2346 },
2347 {
2348 "duration" : 0.33898305084744607,
2349 "fadeIn" : 0,
2350 "fadeOut" : 0,
2351 "id" : "FD19E6CE-504A-42B5-96A0-1CD953096F29",
2352 "kind" : "audio",
2353 "linkId" : "00C998B7-CF37-4D26-B09F-6C36B9B32D82",
2354 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
2355 "muted" : true,
2356 "newShot" : false,
2357 "speed" : 1,
2358 "srcIn" : 582.3229081196761,
2359 "start" : 221.25423728813558,
2360 "track" : "v1"
2361 },
2362 {
2363 "duration" : 0.33898305084744607,
2364 "fadeIn" : 0,
2365 "fadeOut" : 0,
2366 "id" : "A112502F-0EDC-4D49-9AB8-9ACA09DFCDE5",
2367 "kind" : "audio",
2368 "linkId" : "00C998B7-CF37-4D26-B09F-6C36B9B32D82",
2369 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
2370 "muted" : false,
2371 "newShot" : false,
2372 "speed" : 1,
2373 "srcIn" : 582.369584422907,
2374 "start" : 221.25423728813558,
2375 "track" : "v2"
2376 },
2377 {
2378 "duration" : 0.33898305084744607,
2379 "fadeIn" : 0,
2380 "fadeOut" : 0,
2381 "id" : "F01A73DE-9729-484B-89E7-719D0391276E",
2382 "kind" : "video",
2383 "linkId" : "00C998B7-CF37-4D26-B09F-6C36B9B32D82",
2384 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
2385 "muted" : false,
2386 "newShot" : false,
2387 "speed" : 1,
2388 "srcIn" : 582.2329772866789,
2389 "start" : 221.25423728813558,
2390 "track" : "v3"
2391 },
2392 {
2393 "duration" : 0.33898305084744607,
2394 "fadeIn" : 0,
2395 "fadeOut" : 0,
2396 "id" : "627B2BC3-48D0-4F6C-A129-90620B0EB3FD",
2397 "kind" : "video",
2398 "linkId" : "00C998B7-CF37-4D26-B09F-6C36B9B32D82",
2399 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
2400 "muted" : false,
2401 "newShot" : false,
2402 "speed" : 1,
2403 "srcIn" : 582.2329727026801,
2404 "start" : 221.25423728813558,
2405 "track" : "v4"
2406 },
2407 {
2408 "duration" : 0.47457627118643586,
2409 "fadeIn" : 0,
2410 "fadeOut" : 0,
2411 "id" : "DC2DEF3C-5F2B-476E-AF27-8A22D6D9AED7",
2412 "kind" : "video",
2413 "linkId" : "699AF368-3D93-47C1-8338-C3D28F300CB5",
2414 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
2415 "muted" : false,
2416 "newShot" : false,
2417 "speed" : 1,
2418 "srcIn" : 583.1864406779663,
2419 "start" : 221.59322033898303,
2420 "track" : "v0"
2421 },
2422 {
2423 "duration" : 0.47457627118643586,
2424 "fadeIn" : 0,
2425 "fadeOut" : 0,
2426 "id" : "F714792D-CAB4-45F1-9BD8-36A1DF081EB8",
2427 "kind" : "audio",
2428 "linkId" : "699AF368-3D93-47C1-8338-C3D28F300CB5",
2429 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
2430 "muted" : true,
2431 "newShot" : false,
2432 "speed" : 1,
2433 "srcIn" : 583.1025691366252,
2434 "start" : 221.59322033898303,
2435 "track" : "v1"
2436 },
2437 {
2438 "duration" : 0.47457627118643586,
2439 "fadeIn" : 0,
2440 "fadeOut" : 0,
2441 "id" : "BA43BF71-4C54-4370-863D-D0B445C4A4DF",
2442 "kind" : "audio",
2443 "linkId" : "699AF368-3D93-47C1-8338-C3D28F300CB5",
2444 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
2445 "muted" : false,
2446 "newShot" : false,
2447 "speed" : 1,
2448 "srcIn" : 583.1492454398561,
2449 "start" : 221.59322033898303,
2450 "track" : "v2"
2451 },
2452 {
2453 "duration" : 0.47457627118643586,
2454 "fadeIn" : 0,
2455 "fadeOut" : 0,
2456 "id" : "4BAC786B-BDD8-4676-9B29-2A7AC95177F6",
2457 "kind" : "video",
2458 "linkId" : "699AF368-3D93-47C1-8338-C3D28F300CB5",
2459 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
2460 "muted" : false,
2461 "newShot" : false,
2462 "speed" : 1,
2463 "srcIn" : 583.012638303628,
2464 "start" : 221.59322033898303,
2465 "track" : "v3"
2466 },
2467 {
2468 "duration" : 0.47457627118643586,
2469 "fadeIn" : 0,
2470 "fadeOut" : 0,
2471 "id" : "987F92AC-8229-449F-BA8F-BB01CA7CADC3",
2472 "kind" : "video",
2473 "linkId" : "699AF368-3D93-47C1-8338-C3D28F300CB5",
2474 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
2475 "muted" : false,
2476 "newShot" : false,
2477 "speed" : 1,
2478 "srcIn" : 583.0126337196292,
2479 "start" : 221.59322033898303,
2480 "track" : "v4"
2481 },
2482 {
2483 "duration" : 4.813559322033882,
2484 "fadeIn" : 0,
2485 "fadeOut" : 0,
2486 "id" : "8B6B0536-EAC5-4272-9106-03B9E89E36AB",
2487 "kind" : "video",
2488 "linkId" : "0F2B0909-5C81-4A61-BBD8-6CD3ECC9563D",
2489 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
2490 "muted" : false,
2491 "newShot" : false,
2492 "speed" : 1,
2493 "srcIn" : 589.6949152542375,
2494 "start" : 226.67796610169492,
2495 "track" : "v0"
2496 },
2497 {
2498 "duration" : 4.813559322033882,
2499 "fadeIn" : 0,
2500 "fadeOut" : 0,
2501 "id" : "DD50D9F8-4C08-47DA-AB75-382FE215D3C0",
2502 "kind" : "audio",
2503 "linkId" : "0F2B0909-5C81-4A61-BBD8-6CD3ECC9563D",
2504 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
2505 "muted" : true,
2506 "newShot" : false,
2507 "speed" : 1,
2508 "srcIn" : 589.6110437128964,
2509 "start" : 226.67796610169492,
2510 "track" : "v1"
2511 },
2512 {
2513 "duration" : 4.813559322033882,
2514 "fadeIn" : 0,
2515 "fadeOut" : 0,
2516 "id" : "A2113A02-7099-45C4-990B-263F71229BAA",
2517 "kind" : "audio",
2518 "linkId" : "0F2B0909-5C81-4A61-BBD8-6CD3ECC9563D",
2519 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
2520 "muted" : false,
2521 "newShot" : false,
2522 "speed" : 1,
2523 "srcIn" : 589.6577200161273,
2524 "start" : 226.67796610169492,
2525 "track" : "v2"
2526 },
2527 {
2528 "duration" : 4.813559322033882,
2529 "fadeIn" : 0,
2530 "fadeOut" : 0,
2531 "id" : "72B5DADE-2448-445A-A7AB-6BA636E753FD",
2532 "kind" : "video",
2533 "linkId" : "0F2B0909-5C81-4A61-BBD8-6CD3ECC9563D",
2534 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
2535 "muted" : false,
2536 "newShot" : false,
2537 "speed" : 1,
2538 "srcIn" : 589.5211128798992,
2539 "start" : 226.67796610169492,
2540 "track" : "v3"
2541 },
2542 {
2543 "duration" : 4.813559322033882,
2544 "fadeIn" : 0,
2545 "fadeOut" : 0,
2546 "id" : "8CC675CB-9ABE-45FF-B384-C7AC4D78F091",
2547 "kind" : "video",
2548 "linkId" : "0F2B0909-5C81-4A61-BBD8-6CD3ECC9563D",
2549 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
2550 "muted" : false,
2551 "newShot" : false,
2552 "speed" : 1,
2553 "srcIn" : 589.5211082959004,
2554 "start" : 226.67796610169492,
2555 "track" : "v4"
2556 },
2557 {
2558 "duration" : 4.81355932203391,
2559 "fadeIn" : 0,
2560 "fadeOut" : 0,
2561 "id" : "EF0E9A54-F218-4203-8874-E267C94F96ED",
2562 "kind" : "video",
2563 "linkId" : "C9B743A1-0C1E-4522-9FAE-C8D05A31C193",
2564 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
2565 "muted" : false,
2566 "newShot" : false,
2567 "speed" : 1,
2568 "srcIn" : 594.5084745762713,
2569 "start" : 231.4915254237288,
2570 "track" : "v0"
2571 },
2572 {
2573 "duration" : 4.81355932203391,
2574 "fadeIn" : 0,
2575 "fadeOut" : 0,
2576 "id" : "3E1A7EAE-EA47-443C-A5DF-51295361CA28",
2577 "kind" : "audio",
2578 "linkId" : "C9B743A1-0C1E-4522-9FAE-C8D05A31C193",
2579 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
2580 "muted" : true,
2581 "newShot" : false,
2582 "speed" : 1,
2583 "srcIn" : 594.4246030349302,
2584 "start" : 231.4915254237288,
2585 "track" : "v1"
2586 },
2587 {
2588 "duration" : 4.81355932203391,
2589 "fadeIn" : 0,
2590 "fadeOut" : 0,
2591 "id" : "63928E72-332C-4FF0-AB20-7C4D9674F55F",
2592 "kind" : "audio",
2593 "linkId" : "C9B743A1-0C1E-4522-9FAE-C8D05A31C193",
2594 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
2595 "muted" : false,
2596 "newShot" : false,
2597 "speed" : 1,
2598 "srcIn" : 594.4712793381611,
2599 "start" : 231.4915254237288,
2600 "track" : "v2"
2601 },
2602 {
2603 "duration" : 4.81355932203391,
2604 "fadeIn" : 0,
2605 "fadeOut" : 0,
2606 "id" : "AF037655-401A-4032-866C-0E9B1BB6EAC8",
2607 "kind" : "video",
2608 "linkId" : "C9B743A1-0C1E-4522-9FAE-C8D05A31C193",
2609 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
2610 "muted" : false,
2611 "newShot" : false,
2612 "speed" : 1,
2613 "srcIn" : 594.334672201933,
2614 "start" : 231.4915254237288,
2615 "track" : "v3"
2616 },
2617 {
2618 "duration" : 4.81355932203391,
2619 "fadeIn" : 0,
2620 "fadeOut" : 0,
2621 "id" : "AF0F661D-9868-49AB-9276-E20CC57DBC28",
2622 "kind" : "video",
2623 "linkId" : "C9B743A1-0C1E-4522-9FAE-C8D05A31C193",
2624 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
2625 "muted" : false,
2626 "newShot" : false,
2627 "speed" : 1,
2628 "srcIn" : 594.3346676179342,
2629 "start" : 231.4915254237288,
2630 "track" : "v4"
2631 },
2632 {
2633 "duration" : 2.13559322033899,
2634 "fadeIn" : 0,
2635 "fadeOut" : 0,
2636 "id" : "EBA9BB70-0CEA-4170-9DBC-65B5D2FE9AD5",
2637 "kind" : "video",
2638 "linkId" : "4AAA7ABF-1577-4C2E-BF38-8705136C6450",
2639 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
2640 "muted" : false,
2641 "newShot" : false,
2642 "speed" : 1,
2643 "srcIn" : 616.8813559322035,
2644 "start" : 236.3050847457627,
2645 "track" : "v0"
2646 },
2647 {
2648 "duration" : 2.13559322033899,
2649 "fadeIn" : 0,
2650 "fadeOut" : 0,
2651 "id" : "A8D715D7-2371-400B-A53A-90BFA9CA14E1",
2652 "kind" : "audio",
2653 "linkId" : "4AAA7ABF-1577-4C2E-BF38-8705136C6450",
2654 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
2655 "muted" : true,
2656 "newShot" : false,
2657 "speed" : 1,
2658 "srcIn" : 616.7974843908625,
2659 "start" : 236.3050847457627,
2660 "track" : "v1"
2661 },
2662 {
2663 "duration" : 2.13559322033899,
2664 "fadeIn" : 0,
2665 "fadeOut" : 0,
2666 "id" : "1EC0AD88-9036-49C3-99E3-32F0047EB028",
2667 "kind" : "audio",
2668 "linkId" : "4AAA7ABF-1577-4C2E-BF38-8705136C6450",
2669 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
2670 "muted" : false,
2671 "newShot" : false,
2672 "speed" : 1,
2673 "srcIn" : 616.8441606940934,
2674 "start" : 236.3050847457627,
2675 "track" : "v2"
2676 },
2677 {
2678 "duration" : 2.13559322033899,
2679 "fadeIn" : 0,
2680 "fadeOut" : 0,
2681 "id" : "A1C1C0D1-CF81-42A4-8930-B00C4C02387B",
2682 "kind" : "video",
2683 "linkId" : "4AAA7ABF-1577-4C2E-BF38-8705136C6450",
2684 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
2685 "muted" : false,
2686 "newShot" : false,
2687 "speed" : 1,
2688 "srcIn" : 616.7075535578653,
2689 "start" : 236.3050847457627,
2690 "track" : "v3"
2691 },
2692 {
2693 "duration" : 2.13559322033899,
2694 "fadeIn" : 0,
2695 "fadeOut" : 0,
2696 "id" : "9694A094-9FA8-4451-BBC8-3A505816AB5C",
2697 "kind" : "video",
2698 "linkId" : "4AAA7ABF-1577-4C2E-BF38-8705136C6450",
2699 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
2700 "muted" : false,
2701 "newShot" : false,
2702 "speed" : 1,
2703 "srcIn" : 616.7075489738664,
2704 "start" : 236.3050847457627,
2705 "track" : "v4"
2706 },
2707 {
2708 "duration" : 2.2033898305084563,
2709 "fadeIn" : 0,
2710 "fadeOut" : 0,
2711 "id" : "2DFE5FB3-850C-408B-888E-1B117520E619",
2712 "kind" : "video",
2713 "linkId" : "20A4E34D-C55C-439E-9743-C91BA434CB07",
2714 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
2715 "muted" : false,
2716 "newShot" : false,
2717 "speed" : 1,
2718 "srcIn" : 623.1864406779663,
2719 "start" : 238.4406779661017,
2720 "track" : "v0"
2721 },
2722 {
2723 "duration" : 2.2033898305084563,
2724 "fadeIn" : 0,
2725 "fadeOut" : 0,
2726 "id" : "1C5AFA4B-AB07-45EB-9012-D6817BCA669F",
2727 "kind" : "audio",
2728 "linkId" : "20A4E34D-C55C-439E-9743-C91BA434CB07",
2729 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
2730 "muted" : true,
2731 "newShot" : false,
2732 "speed" : 1,
2733 "srcIn" : 623.1025691366252,
2734 "start" : 238.4406779661017,
2735 "track" : "v1"
2736 },
2737 {
2738 "duration" : 2.2033898305084563,
2739 "fadeIn" : 0,
2740 "fadeOut" : 0,
2741 "id" : "FA4004F4-5AA3-4F29-999B-41460017B48F",
2742 "kind" : "audio",
2743 "linkId" : "20A4E34D-C55C-439E-9743-C91BA434CB07",
2744 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
2745 "muted" : false,
2746 "newShot" : false,
2747 "speed" : 1,
2748 "srcIn" : 623.1492454398561,
2749 "start" : 238.4406779661017,
2750 "track" : "v2"
2751 },
2752 {
2753 "duration" : 2.2033898305084563,
2754 "fadeIn" : 0,
2755 "fadeOut" : 0,
2756 "id" : "F4539CE1-8350-4649-A8D5-75DEC9A85285",
2757 "kind" : "video",
2758 "linkId" : "20A4E34D-C55C-439E-9743-C91BA434CB07",
2759 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
2760 "muted" : false,
2761 "newShot" : false,
2762 "speed" : 1,
2763 "srcIn" : 623.012638303628,
2764 "start" : 238.4406779661017,
2765 "track" : "v3"
2766 },
2767 {
2768 "duration" : 2.2033898305084563,
2769 "fadeIn" : 0,
2770 "fadeOut" : 0,
2771 "id" : "B9D12715-E647-49E2-A68A-393FDA9865DF",
2772 "kind" : "video",
2773 "linkId" : "20A4E34D-C55C-439E-9743-C91BA434CB07",
2774 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
2775 "muted" : false,
2776 "newShot" : false,
2777 "speed" : 1,
2778 "srcIn" : 623.0126337196292,
2779 "start" : 238.4406779661017,
2780 "track" : "v4"
2781 },
2782 {
2783 "duration" : 6.27118644067798,
2784 "fadeIn" : 0,
2785 "fadeOut" : 0,
2786 "id" : "92455958-0ACB-484E-B1D9-19C02F73901A",
2787 "kind" : "video",
2788 "linkId" : "FB7680B4-9AF3-40EC-B7FB-010031D66837",
2789 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
2790 "muted" : false,
2791 "newShot" : false,
2792 "speed" : 1,
2793 "srcIn" : 627.491525423729,
2794 "start" : 240.64406779661016,
2795 "track" : "v0"
2796 },
2797 {
2798 "duration" : 6.27118644067798,
2799 "fadeIn" : 0,
2800 "fadeOut" : 0,
2801 "id" : "DB167161-A9FA-4F64-92F8-79A205627364",
2802 "kind" : "audio",
2803 "linkId" : "FB7680B4-9AF3-40EC-B7FB-010031D66837",
2804 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
2805 "muted" : true,
2806 "newShot" : false,
2807 "speed" : 1,
2808 "srcIn" : 627.4076538823879,
2809 "start" : 240.64406779661016,
2810 "track" : "v1"
2811 },
2812 {
2813 "duration" : 6.27118644067798,
2814 "fadeIn" : 0,
2815 "fadeOut" : 0,
2816 "id" : "D53DC51B-6ED3-43B5-90CF-F263E6A8EF0F",
2817 "kind" : "audio",
2818 "linkId" : "FB7680B4-9AF3-40EC-B7FB-010031D66837",
2819 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
2820 "muted" : false,
2821 "newShot" : false,
2822 "speed" : 1,
2823 "srcIn" : 627.4543301856188,
2824 "start" : 240.64406779661016,
2825 "track" : "v2"
2826 },
2827 {
2828 "duration" : 6.27118644067798,
2829 "fadeIn" : 0,
2830 "fadeOut" : 0,
2831 "id" : "0F96EE3E-4FE2-417F-9760-F24B131C949C",
2832 "kind" : "video",
2833 "linkId" : "FB7680B4-9AF3-40EC-B7FB-010031D66837",
2834 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
2835 "muted" : false,
2836 "newShot" : false,
2837 "speed" : 1,
2838 "srcIn" : 627.3177230493907,
2839 "start" : 240.64406779661016,
2840 "track" : "v3"
2841 },
2842 {
2843 "duration" : 6.27118644067798,
2844 "fadeIn" : 0,
2845 "fadeOut" : 0,
2846 "id" : "473DAC6E-6B6F-4395-AA04-1D2A9448459F",
2847 "kind" : "video",
2848 "linkId" : "FB7680B4-9AF3-40EC-B7FB-010031D66837",
2849 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
2850 "muted" : false,
2851 "newShot" : false,
2852 "speed" : 1,
2853 "srcIn" : 627.3177184653919,
2854 "start" : 240.64406779661016,
2855 "track" : "v4"
2856 },
2857 {
2858 "duration" : 5.288135593220346,
2859 "fadeIn" : 0,
2860 "fadeOut" : 0,
2861 "id" : "04295A05-E2E5-4A94-A70F-1C9DCC3ACF86",
2862 "kind" : "video",
2863 "linkId" : "EDE7F931-7884-4F1E-8EFB-E8B00DCB5A07",
2864 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
2865 "muted" : false,
2866 "newShot" : false,
2867 "speed" : 1,
2868 "srcIn" : 641.220338983051,
2869 "start" : 246.91525423728814,
2870 "track" : "v0"
2871 },
2872 {
2873 "duration" : 5.288135593220346,
2874 "fadeIn" : 0,
2875 "fadeOut" : 0,
2876 "id" : "A4446EF1-6B91-4D7B-9285-3C4E42A475ED",
2877 "kind" : "audio",
2878 "linkId" : "EDE7F931-7884-4F1E-8EFB-E8B00DCB5A07",
2879 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
2880 "muted" : true,
2881 "newShot" : false,
2882 "speed" : 1,
2883 "srcIn" : 641.1364674417099,
2884 "start" : 246.91525423728814,
2885 "track" : "v1"
2886 },
2887 {
2888 "duration" : 5.288135593220346,
2889 "fadeIn" : 0,
2890 "fadeOut" : 0,
2891 "id" : "37E22D5C-E179-4CD6-9C69-C6B281846229",
2892 "kind" : "audio",
2893 "linkId" : "EDE7F931-7884-4F1E-8EFB-E8B00DCB5A07",
2894 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
2895 "muted" : false,
2896 "newShot" : false,
2897 "speed" : 1,
2898 "srcIn" : 641.1831437449408,
2899 "start" : 246.91525423728814,
2900 "track" : "v2"
2901 },
2902 {
2903 "duration" : 5.288135593220346,
2904 "fadeIn" : 0,
2905 "fadeOut" : 0,
2906 "id" : "0D2B5334-E420-4746-B09A-110DA7EE6BA5",
2907 "kind" : "video",
2908 "linkId" : "EDE7F931-7884-4F1E-8EFB-E8B00DCB5A07",
2909 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
2910 "muted" : false,
2911 "newShot" : false,
2912 "speed" : 1,
2913 "srcIn" : 641.0465366087127,
2914 "start" : 246.91525423728814,
2915 "track" : "v3"
2916 },
2917 {
2918 "duration" : 5.288135593220346,
2919 "fadeIn" : 0,
2920 "fadeOut" : 0,
2921 "id" : "95AA48BC-4C4B-4F4B-B29F-FB2E01943DE6",
2922 "kind" : "video",
2923 "linkId" : "EDE7F931-7884-4F1E-8EFB-E8B00DCB5A07",
2924 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
2925 "muted" : false,
2926 "newShot" : false,
2927 "speed" : 1,
2928 "srcIn" : 641.0465320247139,
2929 "start" : 246.91525423728814,
2930 "track" : "v4"
2931 },
2932 {
2933 "duration" : 3.3898305084745743,
2934 "fadeIn" : 0,
2935 "fadeOut" : 0,
2936 "id" : "36A8E6B0-4985-4EBD-BD50-2621E6D840E5",
2937 "kind" : "video",
2938 "linkId" : "E1D1EEBE-B0FA-47B6-B02B-0D25E3E7C904",
2939 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
2940 "muted" : false,
2941 "newShot" : false,
2942 "speed" : 1,
2943 "srcIn" : 649.4237288135594,
2944 "start" : 252.20338983050848,
2945 "track" : "v0"
2946 },
2947 {
2948 "duration" : 3.3898305084745743,
2949 "fadeIn" : 0,
2950 "fadeOut" : 0,
2951 "id" : "CDDA95CC-536D-4225-AAC3-470904596C1D",
2952 "kind" : "audio",
2953 "linkId" : "E1D1EEBE-B0FA-47B6-B02B-0D25E3E7C904",
2954 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
2955 "muted" : true,
2956 "newShot" : false,
2957 "speed" : 1,
2958 "srcIn" : 649.3398572722183,
2959 "start" : 252.20338983050848,
2960 "track" : "v1"
2961 },
2962 {
2963 "duration" : 3.3898305084745743,
2964 "fadeIn" : 0,
2965 "fadeOut" : 0,
2966 "id" : "79AC6946-AD60-4D7C-9B74-BAF2C5043ED9",
2967 "kind" : "audio",
2968 "linkId" : "E1D1EEBE-B0FA-47B6-B02B-0D25E3E7C904",
2969 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
2970 "muted" : false,
2971 "newShot" : false,
2972 "speed" : 1,
2973 "srcIn" : 649.3865335754492,
2974 "start" : 252.20338983050848,
2975 "track" : "v2"
2976 },
2977 {
2978 "duration" : 3.3898305084745743,
2979 "fadeIn" : 0,
2980 "fadeOut" : 0,
2981 "id" : "266B1043-1A29-4485-85C8-9921E2D077FE",
2982 "kind" : "video",
2983 "linkId" : "E1D1EEBE-B0FA-47B6-B02B-0D25E3E7C904",
2984 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
2985 "muted" : false,
2986 "newShot" : false,
2987 "speed" : 1,
2988 "srcIn" : 649.2499264392211,
2989 "start" : 252.20338983050848,
2990 "track" : "v3"
2991 },
2992 {
2993 "duration" : 3.3898305084745743,
2994 "fadeIn" : 0,
2995 "fadeOut" : 0,
2996 "id" : "F9D93C0A-48FD-4149-B214-E2648C026BB8",
2997 "kind" : "video",
2998 "linkId" : "E1D1EEBE-B0FA-47B6-B02B-0D25E3E7C904",
2999 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
3000 "muted" : false,
3001 "newShot" : false,
3002 "speed" : 1,
3003 "srcIn" : 649.2499218552223,
3004 "start" : 252.20338983050848,
3005 "track" : "v4"
3006 },
3007 {
3008 "duration" : 8.9491525423729,
3009 "fadeIn" : 0,
3010 "fadeOut" : 0,
3011 "id" : "E13CDB86-D0E7-4219-891E-396AF72F7200",
3012 "kind" : "video",
3013 "linkId" : "E0DD6E6F-D7C7-4743-ADC2-D8787163B0C3",
3014 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
3015 "muted" : false,
3016 "newShot" : false,
3017 "speed" : 1,
3018 "srcIn" : 664.1016949152543,
3019 "start" : 255.59322033898306,
3020 "track" : "v0"
3021 },
3022 {
3023 "duration" : 8.9491525423729,
3024 "fadeIn" : 0,
3025 "fadeOut" : 0,
3026 "id" : "CF093561-83EC-4803-9CD7-E1D07429E06C",
3027 "kind" : "audio",
3028 "linkId" : "E0DD6E6F-D7C7-4743-ADC2-D8787163B0C3",
3029 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
3030 "muted" : true,
3031 "newShot" : false,
3032 "speed" : 1,
3033 "srcIn" : 664.0178233739132,
3034 "start" : 255.59322033898306,
3035 "track" : "v1"
3036 },
3037 {
3038 "duration" : 8.9491525423729,
3039 "fadeIn" : 0,
3040 "fadeOut" : 0,
3041 "id" : "D7A74DDC-3F2A-4E02-B350-45455C959B82",
3042 "kind" : "audio",
3043 "linkId" : "E0DD6E6F-D7C7-4743-ADC2-D8787163B0C3",
3044 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
3045 "muted" : false,
3046 "newShot" : false,
3047 "speed" : 1,
3048 "srcIn" : 664.0644996771441,
3049 "start" : 255.59322033898306,
3050 "track" : "v2"
3051 },
3052 {
3053 "duration" : 8.9491525423729,
3054 "fadeIn" : 0,
3055 "fadeOut" : 0,
3056 "id" : "95E67BFD-C59E-4F99-92B5-027C3BC13CC3",
3057 "kind" : "video",
3058 "linkId" : "E0DD6E6F-D7C7-4743-ADC2-D8787163B0C3",
3059 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
3060 "muted" : false,
3061 "newShot" : false,
3062 "speed" : 1,
3063 "srcIn" : 663.927892540916,
3064 "start" : 255.59322033898306,
3065 "track" : "v3"
3066 },
3067 {
3068 "duration" : 8.9491525423729,
3069 "fadeIn" : 0,
3070 "fadeOut" : 0,
3071 "id" : "AE9DDFED-5BE9-44A5-8349-623A8C1F1587",
3072 "kind" : "video",
3073 "linkId" : "E0DD6E6F-D7C7-4743-ADC2-D8787163B0C3",
3074 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
3075 "muted" : false,
3076 "newShot" : false,
3077 "speed" : 1,
3078 "srcIn" : 663.9278879569172,
3079 "start" : 255.59322033898306,
3080 "track" : "v4"
3081 },
3082 {
3083 "duration" : 2.4067796610169125,
3084 "fadeIn" : 0,
3085 "fadeOut" : 0,
3086 "id" : "6929AB44-D977-4A42-9DD4-69D4E759E4B7",
3087 "kind" : "video",
3088 "linkId" : "B1E3CEAB-46C9-4EF7-8BC4-F413D2FDD063",
3089 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
3090 "muted" : false,
3091 "newShot" : false,
3092 "speed" : 1,
3093 "srcIn" : 676.7796610169491,
3094 "start" : 264.54237288135596,
3095 "track" : "v0"
3096 },
3097 {
3098 "duration" : 2.4067796610169125,
3099 "fadeIn" : 0,
3100 "fadeOut" : 0,
3101 "id" : "F6BA54DA-3AAD-48D5-A132-C946F6FF5BBE",
3102 "kind" : "audio",
3103 "linkId" : "B1E3CEAB-46C9-4EF7-8BC4-F413D2FDD063",
3104 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
3105 "muted" : true,
3106 "newShot" : false,
3107 "speed" : 1,
3108 "srcIn" : 676.6957894756081,
3109 "start" : 264.54237288135596,
3110 "track" : "v1"
3111 },
3112 {
3113 "duration" : 2.4067796610169125,
3114 "fadeIn" : 0,
3115 "fadeOut" : 0,
3116 "id" : "95E0BF0A-76D3-4341-87C0-4C3855C6AD49",
3117 "kind" : "audio",
3118 "linkId" : "B1E3CEAB-46C9-4EF7-8BC4-F413D2FDD063",
3119 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
3120 "muted" : false,
3121 "newShot" : false,
3122 "speed" : 1,
3123 "srcIn" : 676.742465778839,
3124 "start" : 264.54237288135596,
3125 "track" : "v2"
3126 },
3127 {
3128 "duration" : 2.4067796610169125,
3129 "fadeIn" : 0,
3130 "fadeOut" : 0,
3131 "id" : "56B8138F-5F33-4EC1-A202-59403546D6C5",
3132 "kind" : "video",
3133 "linkId" : "B1E3CEAB-46C9-4EF7-8BC4-F413D2FDD063",
3134 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
3135 "muted" : false,
3136 "newShot" : false,
3137 "speed" : 1,
3138 "srcIn" : 676.6058586426109,
3139 "start" : 264.54237288135596,
3140 "track" : "v3"
3141 },
3142 {
3143 "duration" : 2.4067796610169125,
3144 "fadeIn" : 0,
3145 "fadeOut" : 0,
3146 "id" : "7C722B8F-E78E-45E3-8A2E-F30FF2127283",
3147 "kind" : "video",
3148 "linkId" : "B1E3CEAB-46C9-4EF7-8BC4-F413D2FDD063",
3149 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
3150 "muted" : false,
3151 "newShot" : false,
3152 "speed" : 1,
3153 "srcIn" : 676.6058540586121,
3154 "start" : 264.54237288135596,
3155 "track" : "v4"
3156 },
3157 {
3158 "duration" : 4.27118644067798,
3159 "fadeIn" : 0,
3160 "fadeOut" : 0,
3161 "id" : "74A4A856-856E-45BF-BF4B-60A6E5627D38",
3162 "kind" : "video",
3163 "linkId" : "4DA91130-EF31-48CE-9353-1BCA2F4D362F",
3164 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
3165 "muted" : false,
3166 "newShot" : false,
3167 "speed" : 1,
3168 "srcIn" : 680.5084745762712,
3169 "start" : 266.9491525423729,
3170 "track" : "v0"
3171 },
3172 {
3173 "duration" : 4.27118644067798,
3174 "fadeIn" : 0,
3175 "fadeOut" : 0,
3176 "id" : "3DBBE8FE-AD87-457B-8670-E95DE832B26C",
3177 "kind" : "audio",
3178 "linkId" : "4DA91130-EF31-48CE-9353-1BCA2F4D362F",
3179 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
3180 "muted" : true,
3181 "newShot" : false,
3182 "speed" : 1,
3183 "srcIn" : 680.4246030349301,
3184 "start" : 266.9491525423729,
3185 "track" : "v1"
3186 },
3187 {
3188 "duration" : 4.27118644067798,
3189 "fadeIn" : 0,
3190 "fadeOut" : 0,
3191 "id" : "5E2EC015-CDDC-45F6-BEF4-76E842BBC389",
3192 "kind" : "audio",
3193 "linkId" : "4DA91130-EF31-48CE-9353-1BCA2F4D362F",
3194 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
3195 "muted" : false,
3196 "newShot" : false,
3197 "speed" : 1,
3198 "srcIn" : 680.471279338161,
3199 "start" : 266.9491525423729,
3200 "track" : "v2"
3201 },
3202 {
3203 "duration" : 4.27118644067798,
3204 "fadeIn" : 0,
3205 "fadeOut" : 0,
3206 "id" : "1275A44B-DBDA-421C-9443-3923A7274D8D",
3207 "kind" : "video",
3208 "linkId" : "4DA91130-EF31-48CE-9353-1BCA2F4D362F",
3209 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
3210 "muted" : false,
3211 "newShot" : false,
3212 "speed" : 1,
3213 "srcIn" : 680.3346722019329,
3214 "start" : 266.9491525423729,
3215 "track" : "v3"
3216 },
3217 {
3218 "duration" : 4.27118644067798,
3219 "fadeIn" : 0,
3220 "fadeOut" : 0,
3221 "id" : "15AE2513-D3F9-47C3-B4F7-61A351DAB004",
3222 "kind" : "video",
3223 "linkId" : "4DA91130-EF31-48CE-9353-1BCA2F4D362F",
3224 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
3225 "muted" : false,
3226 "newShot" : false,
3227 "speed" : 1,
3228 "srcIn" : 680.3346676179341,
3229 "start" : 266.9491525423729,
3230 "track" : "v4"
3231 },
3232 {
3233 "duration" : 1.1186440677965948,
3234 "fadeIn" : 0,
3235 "fadeOut" : 0,
3236 "id" : "2077C05E-DE10-4841-AB24-8F92DE0579D1",
3237 "kind" : "video",
3238 "linkId" : "870FE72E-0830-4984-8A17-C8265B573862",
3239 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
3240 "muted" : false,
3241 "newShot" : false,
3242 "speed" : 1,
3243 "srcIn" : 698.1016949152543,
3244 "start" : 271.22033898305085,
3245 "track" : "v0"
3246 },
3247 {
3248 "duration" : 1.1186440677965948,
3249 "fadeIn" : 0,
3250 "fadeOut" : 0,
3251 "id" : "1832EFCE-19F8-4EBA-B21A-88826F9AE7BF",
3252 "kind" : "audio",
3253 "linkId" : "870FE72E-0830-4984-8A17-C8265B573862",
3254 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
3255 "muted" : true,
3256 "newShot" : false,
3257 "speed" : 1,
3258 "srcIn" : 698.0178233739132,
3259 "start" : 271.22033898305085,
3260 "track" : "v1"
3261 },
3262 {
3263 "duration" : 1.1186440677965948,
3264 "fadeIn" : 0,
3265 "fadeOut" : 0,
3266 "id" : "4823B8FA-4845-43E7-A4AD-95170EAE1A77",
3267 "kind" : "audio",
3268 "linkId" : "870FE72E-0830-4984-8A17-C8265B573862",
3269 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
3270 "muted" : false,
3271 "newShot" : false,
3272 "speed" : 1,
3273 "srcIn" : 698.0644996771441,
3274 "start" : 271.22033898305085,
3275 "track" : "v2"
3276 },
3277 {
3278 "duration" : 1.1186440677965948,
3279 "fadeIn" : 0,
3280 "fadeOut" : 0,
3281 "id" : "AB47B68B-4217-46FF-A1EA-D21122BC749F",
3282 "kind" : "video",
3283 "linkId" : "870FE72E-0830-4984-8A17-C8265B573862",
3284 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
3285 "muted" : false,
3286 "newShot" : false,
3287 "speed" : 1,
3288 "srcIn" : 697.927892540916,
3289 "start" : 271.22033898305085,
3290 "track" : "v3"
3291 },
3292 {
3293 "duration" : 1.1186440677965948,
3294 "fadeIn" : 0,
3295 "fadeOut" : 0,
3296 "id" : "948310E4-F6E1-4C7B-8DFC-4B1EFE91D9F3",
3297 "kind" : "video",
3298 "linkId" : "870FE72E-0830-4984-8A17-C8265B573862",
3299 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
3300 "muted" : false,
3301 "newShot" : false,
3302 "speed" : 1,
3303 "srcIn" : 697.9278879569172,
3304 "start" : 271.22033898305085,
3305 "track" : "v4"
3306 },
3307 {
3308 "duration" : 3.9661016949152668,
3309 "fadeIn" : 0,
3310 "fadeOut" : 0,
3311 "id" : "1841DCD7-E1AC-46F4-8BA9-65F76CE7F997",
3312 "kind" : "video",
3313 "linkId" : "8E3E2F7E-8CEA-4F62-AC48-EAD5610BBF7A",
3314 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
3315 "muted" : false,
3316 "newShot" : false,
3317 "speed" : 1,
3318 "srcIn" : 706.1016949152543,
3319 "start" : 272.33898305084745,
3320 "track" : "v0"
3321 },
3322 {
3323 "duration" : 3.9661016949152668,
3324 "fadeIn" : 0,
3325 "fadeOut" : 0,
3326 "id" : "9F70CFD3-7AB7-4A6D-87AF-B53BEDECA24A",
3327 "kind" : "audio",
3328 "linkId" : "8E3E2F7E-8CEA-4F62-AC48-EAD5610BBF7A",
3329 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
3330 "muted" : true,
3331 "newShot" : false,
3332 "speed" : 1,
3333 "srcIn" : 706.0178233739132,
3334 "start" : 272.33898305084745,
3335 "track" : "v1"
3336 },
3337 {
3338 "duration" : 3.9661016949152668,
3339 "fadeIn" : 0,
3340 "fadeOut" : 0,
3341 "id" : "6296E4FA-F3A8-4E9F-B5A6-21C104C11316",
3342 "kind" : "audio",
3343 "linkId" : "8E3E2F7E-8CEA-4F62-AC48-EAD5610BBF7A",
3344 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
3345 "muted" : false,
3346 "newShot" : false,
3347 "speed" : 1,
3348 "srcIn" : 706.0644996771441,
3349 "start" : 272.33898305084745,
3350 "track" : "v2"
3351 },
3352 {
3353 "duration" : 3.9661016949152668,
3354 "fadeIn" : 0,
3355 "fadeOut" : 0,
3356 "id" : "7DBC5BCC-4F01-497F-97A9-FD8E5FED80ED",
3357 "kind" : "video",
3358 "linkId" : "8E3E2F7E-8CEA-4F62-AC48-EAD5610BBF7A",
3359 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
3360 "muted" : false,
3361 "newShot" : false,
3362 "speed" : 1,
3363 "srcIn" : 705.927892540916,
3364 "start" : 272.33898305084745,
3365 "track" : "v3"
3366 },
3367 {
3368 "duration" : 3.9661016949152668,
3369 "fadeIn" : 0,
3370 "fadeOut" : 0,
3371 "id" : "D22E7D29-E8F2-49FC-AE39-95140062ED4C",
3372 "kind" : "video",
3373 "linkId" : "8E3E2F7E-8CEA-4F62-AC48-EAD5610BBF7A",
3374 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
3375 "muted" : false,
3376 "newShot" : false,
3377 "speed" : 1,
3378 "srcIn" : 705.9278879569172,
3379 "start" : 272.33898305084745,
3380 "track" : "v4"
3381 },
3382 {
3383 "duration" : 7.2542372881355845,
3384 "fadeIn" : 0,
3385 "fadeOut" : 0,
3386 "id" : "ADE6D5E2-A342-4F16-9FAB-2F40C2D1BCDD",
3387 "kind" : "video",
3388 "linkId" : "7AAFBE23-17BF-4AC0-B4E6-3D003AF75DF9",
3389 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
3390 "muted" : false,
3391 "newShot" : false,
3392 "speed" : 1,
3393 "srcIn" : 710.0677966101696,
3394 "start" : 276.3050847457627,
3395 "track" : "v0"
3396 },
3397 {
3398 "duration" : 7.2542372881355845,
3399 "fadeIn" : 0,
3400 "fadeOut" : 0,
3401 "id" : "BFEEDB13-FCE4-473E-BA8B-BAA795862399",
3402 "kind" : "audio",
3403 "linkId" : "7AAFBE23-17BF-4AC0-B4E6-3D003AF75DF9",
3404 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
3405 "muted" : true,
3406 "newShot" : false,
3407 "speed" : 1,
3408 "srcIn" : 709.9839250688285,
3409 "start" : 276.3050847457627,
3410 "track" : "v1"
3411 },
3412 {
3413 "duration" : 7.2542372881355845,
3414 "fadeIn" : 0,
3415 "fadeOut" : 0,
3416 "id" : "BC83D6FA-84FA-483B-8699-D1219DF29357",
3417 "kind" : "audio",
3418 "linkId" : "7AAFBE23-17BF-4AC0-B4E6-3D003AF75DF9",
3419 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
3420 "muted" : false,
3421 "newShot" : false,
3422 "speed" : 1,
3423 "srcIn" : 710.0306013720594,
3424 "start" : 276.3050847457627,
3425 "track" : "v2"
3426 },
3427 {
3428 "duration" : 7.2542372881355845,
3429 "fadeIn" : 0,
3430 "fadeOut" : 0,
3431 "id" : "58221FDA-00C6-45A1-9267-2AC4500070FE",
3432 "kind" : "video",
3433 "linkId" : "7AAFBE23-17BF-4AC0-B4E6-3D003AF75DF9",
3434 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
3435 "muted" : false,
3436 "newShot" : false,
3437 "speed" : 1,
3438 "srcIn" : 709.8939942358313,
3439 "start" : 276.3050847457627,
3440 "track" : "v3"
3441 },
3442 {
3443 "duration" : 7.2542372881355845,
3444 "fadeIn" : 0,
3445 "fadeOut" : 0,
3446 "id" : "BC23C71D-81CD-40A8-8D4C-5728293351FB",
3447 "kind" : "video",
3448 "linkId" : "7AAFBE23-17BF-4AC0-B4E6-3D003AF75DF9",
3449 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
3450 "muted" : false,
3451 "newShot" : false,
3452 "speed" : 1,
3453 "srcIn" : 709.8939896518325,
3454 "start" : 276.3050847457627,
3455 "track" : "v4"
3456 },
3457 {
3458 "duration" : 7.830508474576277,
3459 "fadeIn" : 0,
3460 "fadeOut" : 0,
3461 "id" : "5B448404-52D6-4D2E-83B3-DDABD2847728",
3462 "kind" : "video",
3463 "linkId" : "B6345D38-3912-4C07-BC43-685A4C767720",
3464 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
3465 "muted" : false,
3466 "newShot" : false,
3467 "speed" : 1,
3468 "srcIn" : 721.6271186440679,
3469 "start" : 283.5593220338983,
3470 "track" : "v0"
3471 },
3472 {
3473 "duration" : 7.830508474576277,
3474 "fadeIn" : 0,
3475 "fadeOut" : 0,
3476 "id" : "EE73D2CB-10F9-47A3-B0A9-B077DC6C6B40",
3477 "kind" : "audio",
3478 "linkId" : "B6345D38-3912-4C07-BC43-685A4C767720",
3479 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
3480 "muted" : true,
3481 "newShot" : false,
3482 "speed" : 1,
3483 "srcIn" : 721.5432471027268,
3484 "start" : 283.5593220338983,
3485 "track" : "v1"
3486 },
3487 {
3488 "duration" : 7.830508474576277,
3489 "fadeIn" : 0,
3490 "fadeOut" : 0,
3491 "id" : "BD06C153-DAD3-449C-B108-1A8130000941",
3492 "kind" : "audio",
3493 "linkId" : "B6345D38-3912-4C07-BC43-685A4C767720",
3494 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
3495 "muted" : false,
3496 "newShot" : false,
3497 "speed" : 1,
3498 "srcIn" : 721.5899234059577,
3499 "start" : 283.5593220338983,
3500 "track" : "v2"
3501 },
3502 {
3503 "duration" : 7.830508474576277,
3504 "fadeIn" : 0,
3505 "fadeOut" : 0,
3506 "id" : "9EFE24ED-F389-4DB3-945B-BF2F18D9915B",
3507 "kind" : "video",
3508 "linkId" : "B6345D38-3912-4C07-BC43-685A4C767720",
3509 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
3510 "muted" : false,
3511 "newShot" : false,
3512 "speed" : 1,
3513 "srcIn" : 721.4533162697296,
3514 "start" : 283.5593220338983,
3515 "track" : "v3"
3516 },
3517 {
3518 "duration" : 7.830508474576277,
3519 "fadeIn" : 0,
3520 "fadeOut" : 0,
3521 "id" : "26D01EC2-D5AF-4693-809B-C57786F0F277",
3522 "kind" : "video",
3523 "linkId" : "B6345D38-3912-4C07-BC43-685A4C767720",
3524 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
3525 "muted" : false,
3526 "newShot" : false,
3527 "speed" : 1,
3528 "srcIn" : 721.4533116857308,
3529 "start" : 283.5593220338983,
3530 "track" : "v4"
3531 },
3532 {
3533 "duration" : 6.27118644067798,
3534 "fadeIn" : 0,
3535 "fadeOut" : 0,
3536 "id" : "6ACBC2E5-88A0-48BD-ABE1-16D4214541B8",
3537 "kind" : "video",
3538 "linkId" : "9DBD3CAA-D3B8-40E4-9DAB-5C8025F8248C",
3539 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
3540 "muted" : false,
3541 "newShot" : false,
3542 "speed" : 1,
3543 "srcIn" : 736.3389830508476,
3544 "start" : 291.3898305084746,
3545 "track" : "v0"
3546 },
3547 {
3548 "duration" : 6.27118644067798,
3549 "fadeIn" : 0,
3550 "fadeOut" : 0,
3551 "id" : "EBF24B46-FE81-45AB-97D4-6FD5E677B893",
3552 "kind" : "audio",
3553 "linkId" : "9DBD3CAA-D3B8-40E4-9DAB-5C8025F8248C",
3554 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
3555 "muted" : true,
3556 "newShot" : false,
3557 "speed" : 1,
3558 "srcIn" : 736.2551115095065,
3559 "start" : 291.3898305084746,
3560 "track" : "v1"
3561 },
3562 {
3563 "duration" : 6.27118644067798,
3564 "fadeIn" : 0,
3565 "fadeOut" : 0,
3566 "id" : "DF4C7EBF-43A7-4CDE-837E-4995C5DE3EE7",
3567 "kind" : "audio",
3568 "linkId" : "9DBD3CAA-D3B8-40E4-9DAB-5C8025F8248C",
3569 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
3570 "muted" : false,
3571 "newShot" : false,
3572 "speed" : 1,
3573 "srcIn" : 736.3017878127374,
3574 "start" : 291.3898305084746,
3575 "track" : "v2"
3576 },
3577 {
3578 "duration" : 6.27118644067798,
3579 "fadeIn" : 0,
3580 "fadeOut" : 0,
3581 "id" : "F39E9B23-7810-4DA0-B17A-6D487A801159",
3582 "kind" : "video",
3583 "linkId" : "9DBD3CAA-D3B8-40E4-9DAB-5C8025F8248C",
3584 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
3585 "muted" : false,
3586 "newShot" : false,
3587 "speed" : 1,
3588 "srcIn" : 736.1651806765093,
3589 "start" : 291.3898305084746,
3590 "track" : "v3"
3591 },
3592 {
3593 "duration" : 6.27118644067798,
3594 "fadeIn" : 0,
3595 "fadeOut" : 0,
3596 "id" : "D58A628D-522C-4267-BA46-9E4D55FE030E",
3597 "kind" : "video",
3598 "linkId" : "9DBD3CAA-D3B8-40E4-9DAB-5C8025F8248C",
3599 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
3600 "muted" : false,
3601 "newShot" : false,
3602 "speed" : 1,
3603 "srcIn" : 736.1651760925105,
3604 "start" : 291.3898305084746,
3605 "track" : "v4"
3606 },
3607 {
3608 "duration" : 9.288135593220318,
3609 "fadeIn" : 0,
3610 "fadeOut" : 0,
3611 "id" : "8A9513E7-2C9C-4EB6-84AE-EDFF80DCF552",
3612 "kind" : "video",
3613 "linkId" : "2D759F83-E902-4851-A3CB-8D0088A77D2D",
3614 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
3615 "muted" : false,
3616 "newShot" : false,
3617 "speed" : 1,
3618 "srcIn" : 747.2881355932204,
3619 "start" : 297.66101694915255,
3620 "track" : "v0"
3621 },
3622 {
3623 "duration" : 9.288135593220318,
3624 "fadeIn" : 0,
3625 "fadeOut" : 0,
3626 "id" : "D7CDE17B-5946-4F71-B09B-6983C216681E",
3627 "kind" : "audio",
3628 "linkId" : "2D759F83-E902-4851-A3CB-8D0088A77D2D",
3629 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
3630 "muted" : true,
3631 "newShot" : false,
3632 "speed" : 1,
3633 "srcIn" : 747.2042640518794,
3634 "start" : 297.66101694915255,
3635 "track" : "v1"
3636 },
3637 {
3638 "duration" : 9.288135593220318,
3639 "fadeIn" : 0,
3640 "fadeOut" : 0,
3641 "id" : "CC586FEC-2BAB-4053-AC79-1FA59BD9BF45",
3642 "kind" : "audio",
3643 "linkId" : "2D759F83-E902-4851-A3CB-8D0088A77D2D",
3644 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
3645 "muted" : false,
3646 "newShot" : false,
3647 "speed" : 1,
3648 "srcIn" : 747.2509403551103,
3649 "start" : 297.66101694915255,
3650 "track" : "v2"
3651 },
3652 {
3653 "duration" : 9.288135593220318,
3654 "fadeIn" : 0,
3655 "fadeOut" : 0,
3656 "id" : "926C01C2-ADAD-4B46-BE04-CD6B4618CCC3",
3657 "kind" : "video",
3658 "linkId" : "2D759F83-E902-4851-A3CB-8D0088A77D2D",
3659 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
3660 "muted" : false,
3661 "newShot" : false,
3662 "speed" : 1,
3663 "srcIn" : 747.1143332188822,
3664 "start" : 297.66101694915255,
3665 "track" : "v3"
3666 },
3667 {
3668 "duration" : 9.288135593220318,
3669 "fadeIn" : 0,
3670 "fadeOut" : 0,
3671 "id" : "0AB6F4F2-89CA-4D4B-A52E-765F89C40923",
3672 "kind" : "video",
3673 "linkId" : "2D759F83-E902-4851-A3CB-8D0088A77D2D",
3674 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
3675 "muted" : false,
3676 "newShot" : false,
3677 "speed" : 1,
3678 "srcIn" : 747.1143286348833,
3679 "start" : 297.66101694915255,
3680 "track" : "v4"
3681 },
3682 {
3683 "duration" : 4.372881355932236,
3684 "fadeIn" : 0,
3685 "fadeOut" : 0,
3686 "id" : "E84D7CB3-1292-4241-AAD4-AA71BA97E643",
3687 "kind" : "video",
3688 "linkId" : "1F9BC1C6-8DE8-4750-ADF1-DBC052261B98",
3689 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
3690 "muted" : false,
3691 "newShot" : false,
3692 "speed" : 1,
3693 "srcIn" : 763.2203389830509,
3694 "start" : 306.9491525423729,
3695 "track" : "v0"
3696 },
3697 {
3698 "duration" : 4.372881355932236,
3699 "fadeIn" : 0,
3700 "fadeOut" : 0,
3701 "id" : "48214E82-E8A7-45D1-A218-C6F71276D005",
3702 "kind" : "audio",
3703 "linkId" : "1F9BC1C6-8DE8-4750-ADF1-DBC052261B98",
3704 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
3705 "muted" : true,
3706 "newShot" : false,
3707 "speed" : 1,
3708 "srcIn" : 763.1364674417098,
3709 "start" : 306.9491525423729,
3710 "track" : "v1"
3711 },
3712 {
3713 "duration" : 4.372881355932236,
3714 "fadeIn" : 0,
3715 "fadeOut" : 0,
3716 "id" : "5D3BAE7E-FE37-43FE-9E8C-945DB4037BD3",
3717 "kind" : "audio",
3718 "linkId" : "1F9BC1C6-8DE8-4750-ADF1-DBC052261B98",
3719 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
3720 "muted" : false,
3721 "newShot" : false,
3722 "speed" : 1,
3723 "srcIn" : 763.1831437449407,
3724 "start" : 306.9491525423729,
3725 "track" : "v2"
3726 },
3727 {
3728 "duration" : 4.372881355932236,
3729 "fadeIn" : 0,
3730 "fadeOut" : 0,
3731 "id" : "6B9196EE-5BAF-4BC6-A372-D785A26AB9B6",
3732 "kind" : "video",
3733 "linkId" : "1F9BC1C6-8DE8-4750-ADF1-DBC052261B98",
3734 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
3735 "muted" : false,
3736 "newShot" : false,
3737 "speed" : 1,
3738 "srcIn" : 763.0465366087126,
3739 "start" : 306.9491525423729,
3740 "track" : "v3"
3741 },
3742 {
3743 "duration" : 4.372881355932236,
3744 "fadeIn" : 0,
3745 "fadeOut" : 0,
3746 "id" : "92FFAC2A-5A18-4CD5-A8E8-38A3237085FB",
3747 "kind" : "video",
3748 "linkId" : "1F9BC1C6-8DE8-4750-ADF1-DBC052261B98",
3749 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
3750 "muted" : false,
3751 "newShot" : false,
3752 "speed" : 1,
3753 "srcIn" : 763.0465320247138,
3754 "start" : 306.9491525423729,
3755 "track" : "v4"
3756 },
3757 {
3758 "duration" : 6.203389830508456,
3759 "fadeIn" : 0,
3760 "fadeOut" : 0,
3761 "id" : "B4E35468-D60F-41C5-9DEB-00DF0D9AB8F0",
3762 "kind" : "video",
3763 "linkId" : "5C054F95-5ECF-452C-8B05-51B9272EC65B",
3764 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
3765 "muted" : false,
3766 "newShot" : false,
3767 "speed" : 1,
3768 "srcIn" : 771.2203389830509,
3769 "start" : 311.3220338983051,
3770 "track" : "v0"
3771 },
3772 {
3773 "duration" : 6.203389830508456,
3774 "fadeIn" : 0,
3775 "fadeOut" : 0,
3776 "id" : "EE8E5E05-B61B-4960-ABF2-9C3490F03B65",
3777 "kind" : "audio",
3778 "linkId" : "5C054F95-5ECF-452C-8B05-51B9272EC65B",
3779 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
3780 "muted" : true,
3781 "newShot" : false,
3782 "speed" : 1,
3783 "srcIn" : 771.1364674417098,
3784 "start" : 311.3220338983051,
3785 "track" : "v1"
3786 },
3787 {
3788 "duration" : 6.203389830508456,
3789 "fadeIn" : 0,
3790 "fadeOut" : 0,
3791 "id" : "41C6BBFA-FF1F-4336-8A02-CC2A7AD8D711",
3792 "kind" : "audio",
3793 "linkId" : "5C054F95-5ECF-452C-8B05-51B9272EC65B",
3794 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
3795 "muted" : false,
3796 "newShot" : false,
3797 "speed" : 1,
3798 "srcIn" : 771.1831437449407,
3799 "start" : 311.3220338983051,
3800 "track" : "v2"
3801 },
3802 {
3803 "duration" : 6.203389830508456,
3804 "fadeIn" : 0,
3805 "fadeOut" : 0,
3806 "id" : "01FAD5BD-4079-4B78-964E-021AB45FD327",
3807 "kind" : "video",
3808 "linkId" : "5C054F95-5ECF-452C-8B05-51B9272EC65B",
3809 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
3810 "muted" : false,
3811 "newShot" : false,
3812 "speed" : 1,
3813 "srcIn" : 771.0465366087126,
3814 "start" : 311.3220338983051,
3815 "track" : "v3"
3816 },
3817 {
3818 "duration" : 6.203389830508456,
3819 "fadeIn" : 0,
3820 "fadeOut" : 0,
3821 "id" : "D05C5F7B-1439-48C3-A549-332D2965E644",
3822 "kind" : "video",
3823 "linkId" : "5C054F95-5ECF-452C-8B05-51B9272EC65B",
3824 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
3825 "muted" : false,
3826 "newShot" : false,
3827 "speed" : 1,
3828 "srcIn" : 771.0465320247138,
3829 "start" : 311.3220338983051,
3830 "track" : "v4"
3831 },
3832 {
3833 "duration" : 4.033898305084733,
3834 "fadeIn" : 0,
3835 "fadeOut" : 0,
3836 "id" : "9ADC6EF4-01E6-44A2-A4E7-CE979C4216A6",
3837 "kind" : "video",
3838 "linkId" : "E7791304-08F7-4757-B49E-97BD758DF635",
3839 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
3840 "muted" : false,
3841 "newShot" : false,
3842 "speed" : 1,
3843 "srcIn" : 780.271186440678,
3844 "start" : 317.52542372881356,
3845 "track" : "v0"
3846 },
3847 {
3848 "duration" : 4.033898305084733,
3849 "fadeIn" : 0,
3850 "fadeOut" : 0,
3851 "id" : "C83F690F-75F2-4F0E-9F44-2B3B64FC4D7D",
3852 "kind" : "audio",
3853 "linkId" : "E7791304-08F7-4757-B49E-97BD758DF635",
3854 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
3855 "muted" : true,
3856 "newShot" : false,
3857 "speed" : 1,
3858 "srcIn" : 780.1873148993369,
3859 "start" : 317.52542372881356,
3860 "track" : "v1"
3861 },
3862 {
3863 "duration" : 4.033898305084733,
3864 "fadeIn" : 0,
3865 "fadeOut" : 0,
3866 "id" : "7F4D1569-EB2B-470A-A39D-4E27BB9631D3",
3867 "kind" : "audio",
3868 "linkId" : "E7791304-08F7-4757-B49E-97BD758DF635",
3869 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
3870 "muted" : false,
3871 "newShot" : false,
3872 "speed" : 1,
3873 "srcIn" : 780.2339912025678,
3874 "start" : 317.52542372881356,
3875 "track" : "v2"
3876 },
3877 {
3878 "duration" : 4.033898305084733,
3879 "fadeIn" : 0,
3880 "fadeOut" : 0,
3881 "id" : "F566D86E-6BCB-450B-B165-E742669968FE",
3882 "kind" : "video",
3883 "linkId" : "E7791304-08F7-4757-B49E-97BD758DF635",
3884 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
3885 "muted" : false,
3886 "newShot" : false,
3887 "speed" : 1,
3888 "srcIn" : 780.0973840663397,
3889 "start" : 317.52542372881356,
3890 "track" : "v3"
3891 },
3892 {
3893 "duration" : 4.033898305084733,
3894 "fadeIn" : 0,
3895 "fadeOut" : 0,
3896 "id" : "397C4077-BE8E-46D8-9A26-4A85FB164ADD",
3897 "kind" : "video",
3898 "linkId" : "E7791304-08F7-4757-B49E-97BD758DF635",
3899 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
3900 "muted" : false,
3901 "newShot" : false,
3902 "speed" : 1,
3903 "srcIn" : 780.0973794823409,
3904 "start" : 317.52542372881356,
3905 "track" : "v4"
3906 },
3907 {
3908 "duration" : 2.474576271186436,
3909 "fadeIn" : 0,
3910 "fadeOut" : 0,
3911 "id" : "3EA1AE5B-B71F-4DD3-BAB2-258343BC38E6",
3912 "kind" : "video",
3913 "linkId" : "9683EB9F-EC01-480F-8BE7-2201B247DE7E",
3914 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
3915 "muted" : false,
3916 "newShot" : false,
3917 "speed" : 1,
3918 "srcIn" : 790.7118644067798,
3919 "start" : 321.5593220338983,
3920 "track" : "v0"
3921 },
3922 {
3923 "duration" : 2.474576271186436,
3924 "fadeIn" : 0,
3925 "fadeOut" : 0,
3926 "id" : "84190D3F-8FAE-46C1-8402-B9BE98C961DD",
3927 "kind" : "audio",
3928 "linkId" : "9683EB9F-EC01-480F-8BE7-2201B247DE7E",
3929 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
3930 "muted" : true,
3931 "newShot" : false,
3932 "speed" : 1,
3933 "srcIn" : 790.6279928654387,
3934 "start" : 321.5593220338983,
3935 "track" : "v1"
3936 },
3937 {
3938 "duration" : 2.474576271186436,
3939 "fadeIn" : 0,
3940 "fadeOut" : 0,
3941 "id" : "BA726F72-FB82-4612-8EBA-361CFF5A177B",
3942 "kind" : "audio",
3943 "linkId" : "9683EB9F-EC01-480F-8BE7-2201B247DE7E",
3944 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
3945 "muted" : false,
3946 "newShot" : false,
3947 "speed" : 1,
3948 "srcIn" : 790.6746691686697,
3949 "start" : 321.5593220338983,
3950 "track" : "v2"
3951 },
3952 {
3953 "duration" : 2.474576271186436,
3954 "fadeIn" : 0,
3955 "fadeOut" : 0,
3956 "id" : "A2BD0CC2-F087-4B62-B210-2422E7CDA62E",
3957 "kind" : "video",
3958 "linkId" : "9683EB9F-EC01-480F-8BE7-2201B247DE7E",
3959 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
3960 "muted" : false,
3961 "newShot" : false,
3962 "speed" : 1,
3963 "srcIn" : 790.5380620324415,
3964 "start" : 321.5593220338983,
3965 "track" : "v3"
3966 },
3967 {
3968 "duration" : 2.474576271186436,
3969 "fadeIn" : 0,
3970 "fadeOut" : 0,
3971 "id" : "CB9AED26-D860-4411-9D20-1B63F56F0BEC",
3972 "kind" : "video",
3973 "linkId" : "9683EB9F-EC01-480F-8BE7-2201B247DE7E",
3974 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
3975 "muted" : false,
3976 "newShot" : false,
3977 "speed" : 1,
3978 "srcIn" : 790.5380574484427,
3979 "start" : 321.5593220338983,
3980 "track" : "v4"
3981 },
3982 {
3983 "duration" : 3.525423728813564,
3984 "fadeIn" : 0,
3985 "fadeOut" : 0,
3986 "id" : "5650BD3B-4BF5-4533-B873-6A89B355DBBD",
3987 "kind" : "video",
3988 "linkId" : "CABF546D-96A6-4629-8BD2-A6CED6EBD7E4",
3989 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
3990 "muted" : false,
3991 "newShot" : false,
3992 "speed" : 1,
3993 "srcIn" : 794.7118644067798,
3994 "start" : 324.03389830508473,
3995 "track" : "v0"
3996 },
3997 {
3998 "duration" : 3.525423728813564,
3999 "fadeIn" : 0,
4000 "fadeOut" : 0,
4001 "id" : "3BA60039-3255-435B-90E4-738D94B13821",
4002 "kind" : "audio",
4003 "linkId" : "CABF546D-96A6-4629-8BD2-A6CED6EBD7E4",
4004 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
4005 "muted" : true,
4006 "newShot" : false,
4007 "speed" : 1,
4008 "srcIn" : 794.6279928654387,
4009 "start" : 324.03389830508473,
4010 "track" : "v1"
4011 },
4012 {
4013 "duration" : 3.525423728813564,
4014 "fadeIn" : 0,
4015 "fadeOut" : 0,
4016 "id" : "1A07AF09-C783-4547-BB45-5671AD0A75E7",
4017 "kind" : "audio",
4018 "linkId" : "CABF546D-96A6-4629-8BD2-A6CED6EBD7E4",
4019 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
4020 "muted" : false,
4021 "newShot" : false,
4022 "speed" : 1,
4023 "srcIn" : 794.6746691686697,
4024 "start" : 324.03389830508473,
4025 "track" : "v2"
4026 },
4027 {
4028 "duration" : 3.525423728813564,
4029 "fadeIn" : 0,
4030 "fadeOut" : 0,
4031 "id" : "A656775F-4507-44FD-ADCB-E23AE9572BD6",
4032 "kind" : "video",
4033 "linkId" : "CABF546D-96A6-4629-8BD2-A6CED6EBD7E4",
4034 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
4035 "muted" : false,
4036 "newShot" : false,
4037 "speed" : 1,
4038 "srcIn" : 794.5380620324415,
4039 "start" : 324.03389830508473,
4040 "track" : "v3"
4041 },
4042 {
4043 "duration" : 3.525423728813564,
4044 "fadeIn" : 0,
4045 "fadeOut" : 0,
4046 "id" : "F287E2E4-9B24-4928-9DF4-BE2A88C6F6E1",
4047 "kind" : "video",
4048 "linkId" : "CABF546D-96A6-4629-8BD2-A6CED6EBD7E4",
4049 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
4050 "muted" : false,
4051 "newShot" : false,
4052 "speed" : 1,
4053 "srcIn" : 794.5380574484427,
4054 "start" : 324.03389830508473,
4055 "track" : "v4"
4056 },
4057 {
4058 "duration" : 11.694915254237287,
4059 "fadeIn" : 0,
4060 "fadeOut" : 0,
4061 "id" : "10BDB7F1-A97E-46B4-AA17-D5AA082F4FE5",
4062 "kind" : "video",
4063 "linkId" : "FE61ADB3-0C95-4A1A-B7F6-D3B4874BD0EA",
4064 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
4065 "muted" : false,
4066 "newShot" : false,
4067 "speed" : 1,
4068 "srcIn" : 812.6440677966104,
4069 "start" : 327.5593220338983,
4070 "track" : "v0"
4071 },
4072 {
4073 "duration" : 11.694915254237287,
4074 "fadeIn" : 0,
4075 "fadeOut" : 0,
4076 "id" : "1A175E00-C9B8-4071-9395-B96E6BBC0766",
4077 "kind" : "audio",
4078 "linkId" : "FE61ADB3-0C95-4A1A-B7F6-D3B4874BD0EA",
4079 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
4080 "muted" : true,
4081 "newShot" : false,
4082 "speed" : 1,
4083 "srcIn" : 812.5601962552694,
4084 "start" : 327.5593220338983,
4085 "track" : "v1"
4086 },
4087 {
4088 "duration" : 11.694915254237287,
4089 "fadeIn" : 0,
4090 "fadeOut" : 0,
4091 "id" : "9CDBFDAC-83C2-4F7B-AC48-618437D267F1",
4092 "kind" : "audio",
4093 "linkId" : "FE61ADB3-0C95-4A1A-B7F6-D3B4874BD0EA",
4094 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
4095 "muted" : false,
4096 "newShot" : false,
4097 "speed" : 1,
4098 "srcIn" : 812.6068725585003,
4099 "start" : 327.5593220338983,
4100 "track" : "v2"
4101 },
4102 {
4103 "duration" : 11.694915254237287,
4104 "fadeIn" : 0,
4105 "fadeOut" : 0,
4106 "id" : "F8DB026C-68E2-4110-8310-A4E77894383C",
4107 "kind" : "video",
4108 "linkId" : "FE61ADB3-0C95-4A1A-B7F6-D3B4874BD0EA",
4109 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
4110 "muted" : false,
4111 "newShot" : false,
4112 "speed" : 1,
4113 "srcIn" : 812.4702654222722,
4114 "start" : 327.5593220338983,
4115 "track" : "v3"
4116 },
4117 {
4118 "duration" : 11.694915254237287,
4119 "fadeIn" : 0,
4120 "fadeOut" : 0,
4121 "id" : "2C6F6969-72A9-49A9-8A8D-A15D2D15A434",
4122 "kind" : "video",
4123 "linkId" : "FE61ADB3-0C95-4A1A-B7F6-D3B4874BD0EA",
4124 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
4125 "muted" : false,
4126 "newShot" : false,
4127 "speed" : 1,
4128 "srcIn" : 812.4702608382734,
4129 "start" : 327.5593220338983,
4130 "track" : "v4"
4131 },
4132 {
4133 "duration" : 1.3898305084745743,
4134 "fadeIn" : 0,
4135 "fadeOut" : 0,
4136 "id" : "5D76FCCF-E20B-4E2B-B2BF-06D04D2EC873",
4137 "kind" : "video",
4138 "linkId" : "5A0236C5-2C98-4C5E-A570-96D211E7B3C6",
4139 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
4140 "muted" : false,
4141 "newShot" : false,
4142 "speed" : 1,
4143 "srcIn" : 980.813559322034,
4144 "start" : 339.2542372881356,
4145 "track" : "v0"
4146 },
4147 {
4148 "duration" : 1.3898305084745743,
4149 "fadeIn" : 0,
4150 "fadeOut" : 0,
4151 "id" : "B834F191-B8D9-4288-A76C-5BC67BBEAA7E",
4152 "kind" : "audio",
4153 "linkId" : "5A0236C5-2C98-4C5E-A570-96D211E7B3C6",
4154 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
4155 "muted" : true,
4156 "newShot" : false,
4157 "speed" : 1,
4158 "srcIn" : 980.729687780693,
4159 "start" : 339.2542372881356,
4160 "track" : "v1"
4161 },
4162 {
4163 "duration" : 1.3898305084745743,
4164 "fadeIn" : 0,
4165 "fadeOut" : 0,
4166 "id" : "EC711F21-C8A3-40C5-B230-A451B6878977",
4167 "kind" : "audio",
4168 "linkId" : "5A0236C5-2C98-4C5E-A570-96D211E7B3C6",
4169 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
4170 "muted" : false,
4171 "newShot" : false,
4172 "speed" : 1,
4173 "srcIn" : 980.7763640839239,
4174 "start" : 339.2542372881356,
4175 "track" : "v2"
4176 },
4177 {
4178 "duration" : 1.3898305084745743,
4179 "fadeIn" : 0,
4180 "fadeOut" : 0,
4181 "id" : "3BFC58C2-D801-45E4-8F01-5F94BDB9ED96",
4182 "kind" : "video",
4183 "linkId" : "5A0236C5-2C98-4C5E-A570-96D211E7B3C6",
4184 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
4185 "muted" : false,
4186 "newShot" : false,
4187 "speed" : 1,
4188 "srcIn" : 980.6397569476958,
4189 "start" : 339.2542372881356,
4190 "track" : "v3"
4191 },
4192 {
4193 "duration" : 1.3898305084745743,
4194 "fadeIn" : 0,
4195 "fadeOut" : 0,
4196 "id" : "28885AEF-445D-47A2-88D3-550BBF692ADE",
4197 "kind" : "video",
4198 "linkId" : "5A0236C5-2C98-4C5E-A570-96D211E7B3C6",
4199 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
4200 "muted" : false,
4201 "newShot" : false,
4202 "speed" : 1,
4203 "srcIn" : 980.639752363697,
4204 "start" : 339.2542372881356,
4205 "track" : "v4"
4206 },
4207 {
4208 "duration" : 0.6440677966101589,
4209 "fadeIn" : 0,
4210 "fadeOut" : 0,
4211 "id" : "EA534368-94B4-475D-8041-45696947070E",
4212 "kind" : "video",
4213 "linkId" : "B31B5062-B2A8-445B-A891-F2D2104E86E3",
4214 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
4215 "muted" : false,
4216 "newShot" : false,
4217 "speed" : 1,
4218 "srcIn" : 983.1186440677968,
4219 "start" : 340.64406779661016,
4220 "track" : "v0"
4221 },
4222 {
4223 "duration" : 0.6440677966101589,
4224 "fadeIn" : 0,
4225 "fadeOut" : 0,
4226 "id" : "F6F6F9A9-8B8B-443A-B401-32205A74C093",
4227 "kind" : "audio",
4228 "linkId" : "B31B5062-B2A8-445B-A891-F2D2104E86E3",
4229 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
4230 "muted" : true,
4231 "newShot" : false,
4232 "speed" : 1,
4233 "srcIn" : 983.0347725264558,
4234 "start" : 340.64406779661016,
4235 "track" : "v1"
4236 },
4237 {
4238 "duration" : 0.6440677966101589,
4239 "fadeIn" : 0,
4240 "fadeOut" : 0,
4241 "id" : "1176FB5D-D780-4273-9FCA-9695A04A100F",
4242 "kind" : "audio",
4243 "linkId" : "B31B5062-B2A8-445B-A891-F2D2104E86E3",
4244 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
4245 "muted" : false,
4246 "newShot" : false,
4247 "speed" : 1,
4248 "srcIn" : 983.0814488296867,
4249 "start" : 340.64406779661016,
4250 "track" : "v2"
4251 },
4252 {
4253 "duration" : 0.6440677966101589,
4254 "fadeIn" : 0,
4255 "fadeOut" : 0,
4256 "id" : "5DA60AD6-AC7E-4579-86CD-50F79A14150D",
4257 "kind" : "video",
4258 "linkId" : "B31B5062-B2A8-445B-A891-F2D2104E86E3",
4259 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
4260 "muted" : false,
4261 "newShot" : false,
4262 "speed" : 1,
4263 "srcIn" : 982.9448416934586,
4264 "start" : 340.64406779661016,
4265 "track" : "v3"
4266 },
4267 {
4268 "duration" : 0.6440677966101589,
4269 "fadeIn" : 0,
4270 "fadeOut" : 0,
4271 "id" : "3DBD1518-ACEA-4CC3-9B85-FB6C1BFDDDE4",
4272 "kind" : "video",
4273 "linkId" : "B31B5062-B2A8-445B-A891-F2D2104E86E3",
4274 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
4275 "muted" : false,
4276 "newShot" : false,
4277 "speed" : 1,
4278 "srcIn" : 982.9448371094597,
4279 "start" : 340.64406779661016,
4280 "track" : "v4"
4281 },
4282 {
4283 "duration" : 3.6271186440678207,
4284 "fadeIn" : 0,
4285 "fadeOut" : 0,
4286 "id" : "832B3ED1-5BC4-43EE-8FE6-717D93008C5A",
4287 "kind" : "video",
4288 "linkId" : "596C8323-127D-44C1-B319-7DF1FB8B1171",
4289 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
4290 "muted" : false,
4291 "newShot" : false,
4292 "speed" : 1,
4293 "srcIn" : 1037.8305084745766,
4294 "start" : 341.2881355932203,
4295 "track" : "v0"
4296 },
4297 {
4298 "duration" : 3.6271186440678207,
4299 "fadeIn" : 0,
4300 "fadeOut" : 0,
4301 "id" : "00209D2F-08A1-4B49-96F2-91A8C5EF454D",
4302 "kind" : "audio",
4303 "linkId" : "596C8323-127D-44C1-B319-7DF1FB8B1171",
4304 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
4305 "muted" : true,
4306 "newShot" : false,
4307 "speed" : 1,
4308 "srcIn" : 1037.7466369332355,
4309 "start" : 341.2881355932203,
4310 "track" : "v1"
4311 },
4312 {
4313 "duration" : 3.6271186440678207,
4314 "fadeIn" : 0,
4315 "fadeOut" : 0,
4316 "id" : "43AEF5F0-29EF-45AD-A7B6-893C574685CB",
4317 "kind" : "audio",
4318 "linkId" : "596C8323-127D-44C1-B319-7DF1FB8B1171",
4319 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
4320 "muted" : false,
4321 "newShot" : false,
4322 "speed" : 1,
4323 "srcIn" : 1037.7933132364665,
4324 "start" : 341.2881355932203,
4325 "track" : "v2"
4326 },
4327 {
4328 "duration" : 3.6271186440678207,
4329 "fadeIn" : 0,
4330 "fadeOut" : 0,
4331 "id" : "32BC4FE2-4816-49E7-80A0-2DA08DCC56A0",
4332 "kind" : "video",
4333 "linkId" : "596C8323-127D-44C1-B319-7DF1FB8B1171",
4334 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
4335 "muted" : false,
4336 "newShot" : false,
4337 "speed" : 1,
4338 "srcIn" : 1037.6567061002384,
4339 "start" : 341.2881355932203,
4340 "track" : "v3"
4341 },
4342 {
4343 "duration" : 3.6271186440678207,
4344 "fadeIn" : 0,
4345 "fadeOut" : 0,
4346 "id" : "7C5CF291-4A7F-4BBD-809E-A9094B0B00E0",
4347 "kind" : "video",
4348 "linkId" : "596C8323-127D-44C1-B319-7DF1FB8B1171",
4349 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
4350 "muted" : false,
4351 "newShot" : false,
4352 "speed" : 1,
4353 "srcIn" : 1037.6567015162395,
4354 "start" : 341.2881355932203,
4355 "track" : "v4"
4356 },
4357 {
4358 "duration" : 1.355932203389841,
4359 "fadeIn" : 0,
4360 "fadeOut" : 0,
4361 "id" : "0905362C-DE86-4A3A-B256-9FD740D3A19D",
4362 "kind" : "video",
4363 "linkId" : "E5D49AA9-6CD5-438D-8B96-6ADA7002033B",
4364 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
4365 "muted" : false,
4366 "newShot" : false,
4367 "speed" : 1,
4368 "srcIn" : 1122.203389830509,
4369 "start" : 344.91525423728814,
4370 "track" : "v0"
4371 },
4372 {
4373 "duration" : 1.355932203389841,
4374 "fadeIn" : 0,
4375 "fadeOut" : 0,
4376 "id" : "48C76EE1-E83E-45E8-A767-47E35F66576F",
4377 "kind" : "audio",
4378 "linkId" : "E5D49AA9-6CD5-438D-8B96-6ADA7002033B",
4379 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
4380 "muted" : true,
4381 "newShot" : false,
4382 "speed" : 1,
4383 "srcIn" : 1122.119518289168,
4384 "start" : 344.91525423728814,
4385 "track" : "v1"
4386 },
4387 {
4388 "duration" : 1.355932203389841,
4389 "fadeIn" : 0,
4390 "fadeOut" : 0,
4391 "id" : "2F82F324-00A9-48EC-BF8C-DAA85B8C9D73",
4392 "kind" : "audio",
4393 "linkId" : "E5D49AA9-6CD5-438D-8B96-6ADA7002033B",
4394 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
4395 "muted" : false,
4396 "newShot" : false,
4397 "speed" : 1,
4398 "srcIn" : 1122.1661945923988,
4399 "start" : 344.91525423728814,
4400 "track" : "v2"
4401 },
4402 {
4403 "duration" : 1.355932203389841,
4404 "fadeIn" : 0,
4405 "fadeOut" : 0,
4406 "id" : "5B27AC9E-88CF-4AB2-927D-A79412E95F09",
4407 "kind" : "video",
4408 "linkId" : "E5D49AA9-6CD5-438D-8B96-6ADA7002033B",
4409 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
4410 "muted" : false,
4411 "newShot" : false,
4412 "speed" : 1,
4413 "srcIn" : 1122.0295874561707,
4414 "start" : 344.91525423728814,
4415 "track" : "v3"
4416 },
4417 {
4418 "duration" : 1.355932203389841,
4419 "fadeIn" : 0,
4420 "fadeOut" : 0,
4421 "id" : "6CC1FC2E-3809-4A37-87B2-3255B1430A3C",
4422 "kind" : "video",
4423 "linkId" : "E5D49AA9-6CD5-438D-8B96-6ADA7002033B",
4424 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
4425 "muted" : false,
4426 "newShot" : false,
4427 "speed" : 1,
4428 "srcIn" : 1122.0295828721719,
4429 "start" : 344.91525423728814,
4430 "track" : "v4"
4431 },
4432 {
4433 "duration" : 5.0847457627118615,
4434 "fadeIn" : 0,
4435 "fadeOut" : 0,
4436 "id" : "F27B9C51-75DE-4E4F-8808-3BA889863CF9",
4437 "kind" : "video",
4438 "linkId" : "81F5C3C3-3A90-4A30-8056-87972464F37F",
4439 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
4440 "muted" : false,
4441 "newShot" : false,
4442 "speed" : 1,
4443 "srcIn" : 1129.254237288136,
4444 "start" : 346.271186440678,
4445 "track" : "v0"
4446 },
4447 {
4448 "duration" : 5.0847457627118615,
4449 "fadeIn" : 0,
4450 "fadeOut" : 0,
4451 "id" : "D6CB9F93-3A96-4AD3-8448-52E45B33A28D",
4452 "kind" : "audio",
4453 "linkId" : "81F5C3C3-3A90-4A30-8056-87972464F37F",
4454 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
4455 "muted" : true,
4456 "newShot" : false,
4457 "speed" : 1,
4458 "srcIn" : 1129.170365746795,
4459 "start" : 346.271186440678,
4460 "track" : "v1"
4461 },
4462 {
4463 "duration" : 5.0847457627118615,
4464 "fadeIn" : 0,
4465 "fadeOut" : 0,
4466 "id" : "34239B0B-3D34-4D29-9C48-D18A775FCDDB",
4467 "kind" : "audio",
4468 "linkId" : "81F5C3C3-3A90-4A30-8056-87972464F37F",
4469 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
4470 "muted" : false,
4471 "newShot" : false,
4472 "speed" : 1,
4473 "srcIn" : 1129.2170420500258,
4474 "start" : 346.271186440678,
4475 "track" : "v2"
4476 },
4477 {
4478 "duration" : 5.0847457627118615,
4479 "fadeIn" : 0,
4480 "fadeOut" : 0,
4481 "id" : "C0EC3471-CC80-448C-BA8B-6F565ED9EE06",
4482 "kind" : "video",
4483 "linkId" : "81F5C3C3-3A90-4A30-8056-87972464F37F",
4484 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
4485 "muted" : false,
4486 "newShot" : false,
4487 "speed" : 1,
4488 "srcIn" : 1129.0804349137977,
4489 "start" : 346.271186440678,
4490 "track" : "v3"
4491 },
4492 {
4493 "duration" : 5.0847457627118615,
4494 "fadeIn" : 0,
4495 "fadeOut" : 0,
4496 "id" : "89A2AE13-B7C0-4138-B475-2D7ED7D1BEEA",
4497 "kind" : "video",
4498 "linkId" : "81F5C3C3-3A90-4A30-8056-87972464F37F",
4499 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
4500 "muted" : false,
4501 "newShot" : false,
4502 "speed" : 1,
4503 "srcIn" : 1129.080430329799,
4504 "start" : 346.271186440678,
4505 "track" : "v4"
4506 },
4507 {
4508 "duration" : 4.711864406779625,
4509 "fadeIn" : 0,
4510 "fadeOut" : 0,
4511 "id" : "2E892202-DFC8-4722-848D-8ED956667E0D",
4512 "kind" : "video",
4513 "linkId" : "CD0169DB-72F4-4E7A-B27D-1D1C0A2437D9",
4514 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
4515 "muted" : false,
4516 "newShot" : false,
4517 "speed" : 1,
4518 "srcIn" : 1278.2372881355936,
4519 "start" : 351.35593220338984,
4520 "track" : "v0"
4521 },
4522 {
4523 "duration" : 4.711864406779625,
4524 "fadeIn" : 0,
4525 "fadeOut" : 0,
4526 "id" : "43D45A86-49A1-449A-9E77-900F5D0ACC61",
4527 "kind" : "audio",
4528 "linkId" : "CD0169DB-72F4-4E7A-B27D-1D1C0A2437D9",
4529 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
4530 "muted" : true,
4531 "newShot" : false,
4532 "speed" : 1,
4533 "srcIn" : 1278.1534165942526,
4534 "start" : 351.35593220338984,
4535 "track" : "v1"
4536 },
4537 {
4538 "duration" : 4.711864406779625,
4539 "fadeIn" : 0,
4540 "fadeOut" : 0,
4541 "id" : "9B4179AA-3FE3-4B93-B578-86F7BDFBAC59",
4542 "kind" : "audio",
4543 "linkId" : "CD0169DB-72F4-4E7A-B27D-1D1C0A2437D9",
4544 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
4545 "muted" : false,
4546 "newShot" : false,
4547 "speed" : 1,
4548 "srcIn" : 1278.2000928974835,
4549 "start" : 351.35593220338984,
4550 "track" : "v2"
4551 },
4552 {
4553 "duration" : 4.711864406779625,
4554 "fadeIn" : 0,
4555 "fadeOut" : 0,
4556 "id" : "84160650-3975-445E-9CCE-FE9A4B354DE4",
4557 "kind" : "video",
4558 "linkId" : "CD0169DB-72F4-4E7A-B27D-1D1C0A2437D9",
4559 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
4560 "muted" : false,
4561 "newShot" : false,
4562 "speed" : 1,
4563 "srcIn" : 1278.0634857612554,
4564 "start" : 351.35593220338984,
4565 "track" : "v3"
4566 },
4567 {
4568 "duration" : 4.711864406779625,
4569 "fadeIn" : 0,
4570 "fadeOut" : 0,
4571 "id" : "EEE0A850-F637-47A2-A351-78A5E35E2E71",
4572 "kind" : "video",
4573 "linkId" : "CD0169DB-72F4-4E7A-B27D-1D1C0A2437D9",
4574 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
4575 "muted" : false,
4576 "newShot" : false,
4577 "speed" : 1,
4578 "srcIn" : 1278.0634811772566,
4579 "start" : 351.35593220338984,
4580 "track" : "v4"
4581 },
4582 {
4583 "duration" : 12.27118644067798,
4584 "fadeIn" : 0,
4585 "fadeOut" : 0,
4586 "id" : "40E92BD1-0CD5-4846-B649-57DEE758C62A",
4587 "kind" : "video",
4588 "linkId" : "7D4C2DFB-52DA-48A5-B7A5-8C50DE99C254",
4589 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
4590 "muted" : false,
4591 "newShot" : false,
4592 "speed" : 1,
4593 "srcIn" : 1283.4237288135596,
4594 "start" : 356.06779661016947,
4595 "track" : "v0"
4596 },
4597 {
4598 "duration" : 12.27118644067798,
4599 "fadeIn" : 0,
4600 "fadeOut" : 0,
4601 "id" : "A9E11104-5BA1-47B5-A29B-0DB9D895655D",
4602 "kind" : "audio",
4603 "linkId" : "7D4C2DFB-52DA-48A5-B7A5-8C50DE99C254",
4604 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
4605 "muted" : true,
4606 "newShot" : false,
4607 "speed" : 1,
4608 "srcIn" : 1283.3398572722185,
4609 "start" : 356.06779661016947,
4610 "track" : "v1"
4611 },
4612 {
4613 "duration" : 12.27118644067798,
4614 "fadeIn" : 0,
4615 "fadeOut" : 0,
4616 "id" : "EB6EE9AD-7386-4D10-BFBF-E253B17F3431",
4617 "kind" : "audio",
4618 "linkId" : "7D4C2DFB-52DA-48A5-B7A5-8C50DE99C254",
4619 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
4620 "muted" : false,
4621 "newShot" : false,
4622 "speed" : 1,
4623 "srcIn" : 1283.3865335754494,
4624 "start" : 356.06779661016947,
4625 "track" : "v2"
4626 },
4627 {
4628 "duration" : 12.27118644067798,
4629 "fadeIn" : 0,
4630 "fadeOut" : 0,
4631 "id" : "438EA227-D2C5-4532-9A3A-252007721608",
4632 "kind" : "video",
4633 "linkId" : "7D4C2DFB-52DA-48A5-B7A5-8C50DE99C254",
4634 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
4635 "muted" : false,
4636 "newShot" : false,
4637 "speed" : 1,
4638 "srcIn" : 1283.2499264392213,
4639 "start" : 356.06779661016947,
4640 "track" : "v3"
4641 },
4642 {
4643 "duration" : 12.27118644067798,
4644 "fadeIn" : 0,
4645 "fadeOut" : 0,
4646 "id" : "E7E7A787-8710-4FC3-8D04-8FB4A9808A63",
4647 "kind" : "video",
4648 "linkId" : "7D4C2DFB-52DA-48A5-B7A5-8C50DE99C254",
4649 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
4650 "muted" : false,
4651 "newShot" : false,
4652 "speed" : 1,
4653 "srcIn" : 1283.2499218552225,
4654 "start" : 356.06779661016947,
4655 "track" : "v4"
4656 },
4657 {
4658 "duration" : 3.0508474576271283,
4659 "fadeIn" : 0,
4660 "fadeOut" : 0,
4661 "id" : "65671782-CE0B-49E2-AF2D-C5D2D41FFA24",
4662 "kind" : "video",
4663 "linkId" : "D50E12ED-A0DF-4078-95DE-6D5332CDFB62",
4664 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
4665 "muted" : false,
4666 "newShot" : false,
4667 "speed" : 1,
4668 "srcIn" : 1304.271186440678,
4669 "start" : 368.33898305084745,
4670 "track" : "v0"
4671 },
4672 {
4673 "duration" : 3.0508474576271283,
4674 "fadeIn" : 0,
4675 "fadeOut" : 0,
4676 "id" : "B250A8E7-5DB5-463B-BC7E-2A985C554A48",
4677 "kind" : "audio",
4678 "linkId" : "D50E12ED-A0DF-4078-95DE-6D5332CDFB62",
4679 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
4680 "muted" : true,
4681 "newShot" : false,
4682 "speed" : 1,
4683 "srcIn" : 1304.187314899337,
4684 "start" : 368.33898305084745,
4685 "track" : "v1"
4686 },
4687 {
4688 "duration" : 3.0508474576271283,
4689 "fadeIn" : 0,
4690 "fadeOut" : 0,
4691 "id" : "538F899C-5E20-49EC-8416-74178778FE07",
4692 "kind" : "audio",
4693 "linkId" : "D50E12ED-A0DF-4078-95DE-6D5332CDFB62",
4694 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
4695 "muted" : false,
4696 "newShot" : false,
4697 "speed" : 1,
4698 "srcIn" : 1304.233991202568,
4699 "start" : 368.33898305084745,
4700 "track" : "v2"
4701 },
4702 {
4703 "duration" : 3.0508474576271283,
4704 "fadeIn" : 0,
4705 "fadeOut" : 0,
4706 "id" : "AB06969E-2217-4DDA-B6A6-0537DB89EDD0",
4707 "kind" : "video",
4708 "linkId" : "D50E12ED-A0DF-4078-95DE-6D5332CDFB62",
4709 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
4710 "muted" : false,
4711 "newShot" : false,
4712 "speed" : 1,
4713 "srcIn" : 1304.0973840663398,
4714 "start" : 368.33898305084745,
4715 "track" : "v3"
4716 },
4717 {
4718 "duration" : 3.0508474576271283,
4719 "fadeIn" : 0,
4720 "fadeOut" : 0,
4721 "id" : "84921634-8C3A-4FDC-9737-68D94154D037",
4722 "kind" : "video",
4723 "linkId" : "D50E12ED-A0DF-4078-95DE-6D5332CDFB62",
4724 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
4725 "muted" : false,
4726 "newShot" : false,
4727 "speed" : 1,
4728 "srcIn" : 1304.097379482341,
4729 "start" : 368.33898305084745,
4730 "track" : "v4"
4731 },
4732 {
4733 "duration" : 6.610169491525426,
4734 "fadeIn" : 0,
4735 "fadeOut" : 0,
4736 "id" : "B2E2127E-A4B7-4682-BC09-FE4FBF53EC66",
4737 "kind" : "video",
4738 "linkId" : "4A3BE2AA-0118-46FB-9311-40602A8063DF",
4739 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
4740 "muted" : false,
4741 "newShot" : false,
4742 "speed" : 1,
4743 "srcIn" : 1309.6610169491526,
4744 "start" : 371.3898305084746,
4745 "track" : "v0"
4746 },
4747 {
4748 "duration" : 6.610169491525426,
4749 "fadeIn" : 0,
4750 "fadeOut" : 0,
4751 "id" : "A2F9926B-983E-4F53-99C7-92BB9B023122",
4752 "kind" : "audio",
4753 "linkId" : "4A3BE2AA-0118-46FB-9311-40602A8063DF",
4754 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
4755 "muted" : true,
4756 "newShot" : false,
4757 "speed" : 1,
4758 "srcIn" : 1309.5771454078115,
4759 "start" : 371.3898305084746,
4760 "track" : "v1"
4761 },
4762 {
4763 "duration" : 6.610169491525426,
4764 "fadeIn" : 0,
4765 "fadeOut" : 0,
4766 "id" : "D13D2198-79F9-4915-A5E0-AC690ADBC371",
4767 "kind" : "audio",
4768 "linkId" : "4A3BE2AA-0118-46FB-9311-40602A8063DF",
4769 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
4770 "muted" : false,
4771 "newShot" : false,
4772 "speed" : 1,
4773 "srcIn" : 1309.6238217110424,
4774 "start" : 371.3898305084746,
4775 "track" : "v2"
4776 },
4777 {
4778 "duration" : 6.610169491525426,
4779 "fadeIn" : 0,
4780 "fadeOut" : 0,
4781 "id" : "53651C3A-4919-49F0-ADED-34CDF520F07E",
4782 "kind" : "video",
4783 "linkId" : "4A3BE2AA-0118-46FB-9311-40602A8063DF",
4784 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
4785 "muted" : false,
4786 "newShot" : false,
4787 "speed" : 1,
4788 "srcIn" : 1309.4872145748143,
4789 "start" : 371.3898305084746,
4790 "track" : "v3"
4791 },
4792 {
4793 "duration" : 6.610169491525426,
4794 "fadeIn" : 0,
4795 "fadeOut" : 0,
4796 "id" : "00F70279-9C4E-40AB-8D35-B686234D9CD2",
4797 "kind" : "video",
4798 "linkId" : "4A3BE2AA-0118-46FB-9311-40602A8063DF",
4799 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
4800 "muted" : false,
4801 "newShot" : false,
4802 "speed" : 1,
4803 "srcIn" : 1309.4872099908155,
4804 "start" : 371.3898305084746,
4805 "track" : "v4"
4806 },
4807 {
4808 "duration" : 6.372881355932179,
4809 "fadeIn" : 0,
4810 "fadeOut" : 0,
4811 "id" : "3EB22ECC-5EAD-4107-9F24-6864C9464EAC",
4812 "kind" : "video",
4813 "linkId" : "346B7025-BD91-4339-98B4-DBE997BC1A27",
4814 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
4815 "muted" : false,
4816 "newShot" : false,
4817 "speed" : 1,
4818 "srcIn" : 1319.1186440677966,
4819 "start" : 378,
4820 "track" : "v0"
4821 },
4822 {
4823 "duration" : 6.372881355932179,
4824 "fadeIn" : 0,
4825 "fadeOut" : 0,
4826 "id" : "2B2021EE-7138-45C6-A0CF-0D8BBAC1116A",
4827 "kind" : "audio",
4828 "linkId" : "346B7025-BD91-4339-98B4-DBE997BC1A27",
4829 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
4830 "muted" : true,
4831 "newShot" : false,
4832 "speed" : 1,
4833 "srcIn" : 1319.0347725264555,
4834 "start" : 378,
4835 "track" : "v1"
4836 },
4837 {
4838 "duration" : 6.372881355932179,
4839 "fadeIn" : 0,
4840 "fadeOut" : 0,
4841 "id" : "4A8A3D03-F2FB-4651-A609-318676737F28",
4842 "kind" : "audio",
4843 "linkId" : "346B7025-BD91-4339-98B4-DBE997BC1A27",
4844 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
4845 "muted" : false,
4846 "newShot" : false,
4847 "speed" : 1,
4848 "srcIn" : 1319.0814488296865,
4849 "start" : 378,
4850 "track" : "v2"
4851 },
4852 {
4853 "duration" : 6.372881355932179,
4854 "fadeIn" : 0,
4855 "fadeOut" : 0,
4856 "id" : "196CB0CE-61ED-4852-A694-AB8B592CF510",
4857 "kind" : "video",
4858 "linkId" : "346B7025-BD91-4339-98B4-DBE997BC1A27",
4859 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
4860 "muted" : false,
4861 "newShot" : false,
4862 "speed" : 1,
4863 "srcIn" : 1318.9448416934583,
4864 "start" : 378,
4865 "track" : "v3"
4866 },
4867 {
4868 "duration" : 6.372881355932179,
4869 "fadeIn" : 0,
4870 "fadeOut" : 0,
4871 "id" : "AB9E730D-3DB3-430E-92B1-8528951D9146",
4872 "kind" : "video",
4873 "linkId" : "346B7025-BD91-4339-98B4-DBE997BC1A27",
4874 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
4875 "muted" : false,
4876 "newShot" : false,
4877 "speed" : 1,
4878 "srcIn" : 1318.9448371094595,
4879 "start" : 378,
4880 "track" : "v4"
4881 },
4882 {
4883 "duration" : 10.983050847457662,
4884 "fadeIn" : 0,
4885 "fadeOut" : 0,
4886 "id" : "59AB16D3-EBCD-438B-8B8C-88413E1E8D67",
4887 "kind" : "video",
4888 "linkId" : "8A7CFF6A-2FE9-4C3F-813E-9DDC903D1C26",
4889 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
4890 "muted" : false,
4891 "newShot" : false,
4892 "speed" : 1,
4893 "srcIn" : 1327.1186440677966,
4894 "start" : 384.3728813559322,
4895 "track" : "v0"
4896 },
4897 {
4898 "duration" : 10.983050847457662,
4899 "fadeIn" : 0,
4900 "fadeOut" : 0,
4901 "id" : "77501D06-4929-403D-8FCB-6E79E9A296A4",
4902 "kind" : "audio",
4903 "linkId" : "8A7CFF6A-2FE9-4C3F-813E-9DDC903D1C26",
4904 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
4905 "muted" : true,
4906 "newShot" : false,
4907 "speed" : 1,
4908 "srcIn" : 1327.0347725264555,
4909 "start" : 384.3728813559322,
4910 "track" : "v1"
4911 },
4912 {
4913 "duration" : 10.983050847457662,
4914 "fadeIn" : 0,
4915 "fadeOut" : 0,
4916 "id" : "A8B5D5C7-311F-4D71-BD19-7B1BAC7B047E",
4917 "kind" : "audio",
4918 "linkId" : "8A7CFF6A-2FE9-4C3F-813E-9DDC903D1C26",
4919 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
4920 "muted" : false,
4921 "newShot" : false,
4922 "speed" : 1,
4923 "srcIn" : 1327.0814488296865,
4924 "start" : 384.3728813559322,
4925 "track" : "v2"
4926 },
4927 {
4928 "duration" : 10.983050847457662,
4929 "fadeIn" : 0,
4930 "fadeOut" : 0,
4931 "id" : "F9DB0D5E-9113-4D5E-81AB-EAE9AF22C7FE",
4932 "kind" : "video",
4933 "linkId" : "8A7CFF6A-2FE9-4C3F-813E-9DDC903D1C26",
4934 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
4935 "muted" : false,
4936 "newShot" : false,
4937 "speed" : 1,
4938 "srcIn" : 1326.9448416934583,
4939 "start" : 384.3728813559322,
4940 "track" : "v3"
4941 },
4942 {
4943 "duration" : 10.983050847457662,
4944 "fadeIn" : 0,
4945 "fadeOut" : 0,
4946 "id" : "D4A49349-31F4-41F7-91B5-682C810269F4",
4947 "kind" : "video",
4948 "linkId" : "8A7CFF6A-2FE9-4C3F-813E-9DDC903D1C26",
4949 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
4950 "muted" : false,
4951 "newShot" : false,
4952 "speed" : 1,
4953 "srcIn" : 1326.9448371094595,
4954 "start" : 384.3728813559322,
4955 "track" : "v4"
4956 },
4957 {
4958 "duration" : 14.169491525423723,
4959 "fadeIn" : 0,
4960 "fadeOut" : 0,
4961 "id" : "3BEF9E6B-7901-407C-BEA3-7AFAB2A27DF5",
4962 "kind" : "video",
4963 "linkId" : "704CF8B0-3041-4053-9080-715DFE6B2D25",
4964 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
4965 "muted" : false,
4966 "newShot" : false,
4967 "speed" : 1,
4968 "srcIn" : 1389.0169491525423,
4969 "start" : 395.35593220338984,
4970 "track" : "v0"
4971 },
4972 {
4973 "duration" : 14.169491525423723,
4974 "fadeIn" : 0,
4975 "fadeOut" : 0,
4976 "id" : "BC1D4D97-B973-4693-9B24-5823A5192FAA",
4977 "kind" : "audio",
4978 "linkId" : "704CF8B0-3041-4053-9080-715DFE6B2D25",
4979 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
4980 "muted" : true,
4981 "newShot" : false,
4982 "speed" : 1,
4983 "srcIn" : 1388.9330776112013,
4984 "start" : 395.35593220338984,
4985 "track" : "v1"
4986 },
4987 {
4988 "duration" : 14.169491525423723,
4989 "fadeIn" : 0,
4990 "fadeOut" : 0,
4991 "id" : "DBBCAF21-55F8-44F0-9F8A-4EA556B8EB57",
4992 "kind" : "audio",
4993 "linkId" : "704CF8B0-3041-4053-9080-715DFE6B2D25",
4994 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
4995 "muted" : false,
4996 "newShot" : false,
4997 "speed" : 1,
4998 "srcIn" : 1388.9797539144322,
4999 "start" : 395.35593220338984,
5000 "track" : "v2"
5001 },
5002 {
5003 "duration" : 14.169491525423723,
5004 "fadeIn" : 0,
5005 "fadeOut" : 0,
5006 "id" : "6C7A0EB2-88FC-4404-B48D-AA7FE6269170",
5007 "kind" : "video",
5008 "linkId" : "704CF8B0-3041-4053-9080-715DFE6B2D25",
5009 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
5010 "muted" : false,
5011 "newShot" : false,
5012 "speed" : 1,
5013 "srcIn" : 1388.843146778204,
5014 "start" : 395.35593220338984,
5015 "track" : "v3"
5016 },
5017 {
5018 "duration" : 14.169491525423723,
5019 "fadeIn" : 0,
5020 "fadeOut" : 0,
5021 "id" : "50068A9B-E7CF-4DD9-9917-7155FEEE9EA2",
5022 "kind" : "video",
5023 "linkId" : "704CF8B0-3041-4053-9080-715DFE6B2D25",
5024 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
5025 "muted" : false,
5026 "newShot" : false,
5027 "speed" : 1,
5028 "srcIn" : 1388.8431421942053,
5029 "start" : 395.35593220338984,
5030 "track" : "v4"
5031 },
5032 {
5033 "duration" : 12.610169491525426,
5034 "fadeIn" : 0,
5035 "fadeOut" : 0,
5036 "id" : "EA7CF47C-E4D3-437C-B0E5-4275167856C5",
5037 "kind" : "video",
5038 "linkId" : "5E30C5A7-ECB1-41A5-9456-2F737C45E0C1",
5039 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
5040 "muted" : false,
5041 "newShot" : false,
5042 "speed" : 1,
5043 "srcIn" : 1406.5762711864404,
5044 "start" : 409.52542372881356,
5045 "track" : "v0"
5046 },
5047 {
5048 "duration" : 12.610169491525426,
5049 "fadeIn" : 0,
5050 "fadeOut" : 0,
5051 "id" : "1E9A4B81-6D6C-4CEF-BCD9-A56D14BC12E5",
5052 "kind" : "audio",
5053 "linkId" : "5E30C5A7-ECB1-41A5-9456-2F737C45E0C1",
5054 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
5055 "muted" : true,
5056 "newShot" : false,
5057 "speed" : 1,
5058 "srcIn" : 1406.4923996450993,
5059 "start" : 409.52542372881356,
5060 "track" : "v1"
5061 },
5062 {
5063 "duration" : 12.610169491525426,
5064 "fadeIn" : 0,
5065 "fadeOut" : 0,
5066 "id" : "1506A648-D35B-4619-A082-06FF4E3CBA5B",
5067 "kind" : "audio",
5068 "linkId" : "5E30C5A7-ECB1-41A5-9456-2F737C45E0C1",
5069 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
5070 "muted" : false,
5071 "newShot" : false,
5072 "speed" : 1,
5073 "srcIn" : 1406.5390759483303,
5074 "start" : 409.52542372881356,
5075 "track" : "v2"
5076 },
5077 {
5078 "duration" : 12.610169491525426,
5079 "fadeIn" : 0,
5080 "fadeOut" : 0,
5081 "id" : "9D08D70B-5627-48C7-873D-A8153FA5DA0E",
5082 "kind" : "video",
5083 "linkId" : "5E30C5A7-ECB1-41A5-9456-2F737C45E0C1",
5084 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
5085 "muted" : false,
5086 "newShot" : false,
5087 "speed" : 1,
5088 "srcIn" : 1406.4024688121021,
5089 "start" : 409.52542372881356,
5090 "track" : "v3"
5091 },
5092 {
5093 "duration" : 12.610169491525426,
5094 "fadeIn" : 0,
5095 "fadeOut" : 0,
5096 "id" : "A6181FAE-7AFB-4D19-B593-91540501DC66",
5097 "kind" : "video",
5098 "linkId" : "5E30C5A7-ECB1-41A5-9456-2F737C45E0C1",
5099 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
5100 "muted" : false,
5101 "newShot" : false,
5102 "speed" : 1,
5103 "srcIn" : 1406.4024642281033,
5104 "start" : 409.52542372881356,
5105 "track" : "v4"
5106 },
5107 {
5108 "duration" : 12.13559322033899,
5109 "fadeIn" : 0,
5110 "fadeOut" : 0,
5111 "id" : "4E3F3515-7F7B-447C-BC9F-E4220A2BA2A2",
5112 "kind" : "video",
5113 "linkId" : "226C31FD-B8DF-4F4A-A67B-DCB285B8B836",
5114 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
5115 "muted" : false,
5116 "newShot" : false,
5117 "speed" : 1,
5118 "srcIn" : 1434.1694915254236,
5119 "start" : 428.271186440678,
5120 "track" : "v0"
5121 },
5122 {
5123 "duration" : 12.13559322033899,
5124 "fadeIn" : 0,
5125 "fadeOut" : 0,
5126 "id" : "635A9B0E-7D5C-4F08-BEB1-0D43CEB95071",
5127 "kind" : "audio",
5128 "linkId" : "226C31FD-B8DF-4F4A-A67B-DCB285B8B836",
5129 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
5130 "muted" : true,
5131 "newShot" : false,
5132 "speed" : 1,
5133 "srcIn" : 1434.0856199840825,
5134 "start" : 428.271186440678,
5135 "track" : "v1"
5136 },
5137 {
5138 "duration" : 12.13559322033899,
5139 "fadeIn" : 0,
5140 "fadeOut" : 0,
5141 "id" : "B26C43CA-9142-4FF8-8DE6-5FB3CF02701F",
5142 "kind" : "audio",
5143 "linkId" : "226C31FD-B8DF-4F4A-A67B-DCB285B8B836",
5144 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
5145 "muted" : false,
5146 "newShot" : false,
5147 "speed" : 1,
5148 "srcIn" : 1434.1322962873135,
5149 "start" : 428.271186440678,
5150 "track" : "v2"
5151 },
5152 {
5153 "duration" : 12.13559322033899,
5154 "fadeIn" : 0,
5155 "fadeOut" : 0,
5156 "id" : "1D681666-828B-4516-905F-70C0B9927714",
5157 "kind" : "video",
5158 "linkId" : "226C31FD-B8DF-4F4A-A67B-DCB285B8B836",
5159 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
5160 "muted" : false,
5161 "newShot" : false,
5162 "speed" : 1,
5163 "srcIn" : 1433.9956891510853,
5164 "start" : 428.271186440678,
5165 "track" : "v3"
5166 },
5167 {
5168 "duration" : 12.13559322033899,
5169 "fadeIn" : 0,
5170 "fadeOut" : 0,
5171 "id" : "14FFF5E0-4F74-4347-AEC2-714B77823B90",
5172 "kind" : "video",
5173 "linkId" : "226C31FD-B8DF-4F4A-A67B-DCB285B8B836",
5174 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
5175 "muted" : false,
5176 "newShot" : false,
5177 "speed" : 1,
5178 "srcIn" : 1433.9956845670865,
5179 "start" : 428.271186440678,
5180 "track" : "v4"
5181 },
5182 {
5183 "duration" : 6.13559322033899,
5184 "fadeIn" : 0,
5185 "fadeOut" : 0,
5186 "id" : "651982BC-F62C-433E-9CA6-7F53FDD18AD2",
5187 "kind" : "video",
5188 "linkId" : "985611EC-FA7E-4535-BFEC-AECD319001B6",
5189 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
5190 "muted" : false,
5191 "newShot" : false,
5192 "speed" : 1,
5193 "srcIn" : 1428.0338983050847,
5194 "start" : 422.135593220339,
5195 "track" : "v0"
5196 },
5197 {
5198 "duration" : 6.13559322033899,
5199 "fadeIn" : 0,
5200 "fadeOut" : 0,
5201 "id" : "011DD52B-7DF9-4AB2-9143-9249F293A98C",
5202 "kind" : "audio",
5203 "linkId" : "985611EC-FA7E-4535-BFEC-AECD319001B6",
5204 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
5205 "muted" : true,
5206 "newShot" : false,
5207 "speed" : 1,
5208 "srcIn" : 1427.9500267637436,
5209 "start" : 422.135593220339,
5210 "track" : "v1"
5211 },
5212 {
5213 "duration" : 6.13559322033899,
5214 "fadeIn" : 0,
5215 "fadeOut" : 0,
5216 "id" : "9C6C77CE-A577-41A8-AF62-5B1AD22511CB",
5217 "kind" : "audio",
5218 "linkId" : "985611EC-FA7E-4535-BFEC-AECD319001B6",
5219 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
5220 "muted" : false,
5221 "newShot" : false,
5222 "speed" : 1,
5223 "srcIn" : 1427.9967030669745,
5224 "start" : 422.135593220339,
5225 "track" : "v2"
5226 },
5227 {
5228 "duration" : 6.13559322033899,
5229 "fadeIn" : 0,
5230 "fadeOut" : 0,
5231 "id" : "81445B0A-E2DC-4623-B61E-F78297F1AF9B",
5232 "kind" : "video",
5233 "linkId" : "985611EC-FA7E-4535-BFEC-AECD319001B6",
5234 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
5235 "muted" : false,
5236 "newShot" : false,
5237 "speed" : 1,
5238 "srcIn" : 1427.8600959307464,
5239 "start" : 422.135593220339,
5240 "track" : "v3"
5241 },
5242 {
5243 "duration" : 6.13559322033899,
5244 "fadeIn" : 0,
5245 "fadeOut" : 0,
5246 "id" : "2FCF79AA-A233-43B0-924A-F22316673337",
5247 "kind" : "video",
5248 "linkId" : "985611EC-FA7E-4535-BFEC-AECD319001B6",
5249 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
5250 "muted" : false,
5251 "newShot" : false,
5252 "speed" : 1,
5253 "srcIn" : 1427.8600913467476,
5254 "start" : 422.135593220339,
5255 "track" : "v4"
5256 },
5257 {
5258 "duration" : 9.45762711864404,
5259 "fadeIn" : 0,
5260 "fadeOut" : 0,
5261 "id" : "3226AEDC-71C8-4045-95AB-2938C4E153FF",
5262 "kind" : "video",
5263 "linkId" : "ABBC461D-6EED-494A-8B1A-7677A2E7D027",
5264 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
5265 "muted" : false,
5266 "newShot" : false,
5267 "speed" : 1,
5268 "srcIn" : 1449.559322033898,
5269 "start" : 440.40677966101697,
5270 "track" : "v0"
5271 },
5272 {
5273 "duration" : 9.45762711864404,
5274 "fadeIn" : 0,
5275 "fadeOut" : 0,
5276 "id" : "FAF956D0-C01F-4926-93F2-49FA34A04E13",
5277 "kind" : "audio",
5278 "linkId" : "ABBC461D-6EED-494A-8B1A-7677A2E7D027",
5279 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
5280 "muted" : true,
5281 "newShot" : false,
5282 "speed" : 1,
5283 "srcIn" : 1449.475450492557,
5284 "start" : 440.40677966101697,
5285 "track" : "v1"
5286 },
5287 {
5288 "duration" : 9.45762711864404,
5289 "fadeIn" : 0,
5290 "fadeOut" : 0,
5291 "id" : "3EB9B48B-CACE-4489-BAFE-59EB0F2E6305",
5292 "kind" : "audio",
5293 "linkId" : "ABBC461D-6EED-494A-8B1A-7677A2E7D027",
5294 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
5295 "muted" : false,
5296 "newShot" : false,
5297 "speed" : 1,
5298 "srcIn" : 1449.522126795788,
5299 "start" : 440.40677966101697,
5300 "track" : "v2"
5301 },
5302 {
5303 "duration" : 9.45762711864404,
5304 "fadeIn" : 0,
5305 "fadeOut" : 0,
5306 "id" : "4406B9FA-D633-49BC-BB25-FA2554A8FA6A",
5307 "kind" : "video",
5308 "linkId" : "ABBC461D-6EED-494A-8B1A-7677A2E7D027",
5309 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
5310 "muted" : false,
5311 "newShot" : false,
5312 "speed" : 1,
5313 "srcIn" : 1449.3855196595598,
5314 "start" : 440.40677966101697,
5315 "track" : "v3"
5316 },
5317 {
5318 "duration" : 9.45762711864404,
5319 "fadeIn" : 0,
5320 "fadeOut" : 0,
5321 "id" : "99E4D7B0-8E74-4871-B79C-5EE9AE03E424",
5322 "kind" : "video",
5323 "linkId" : "ABBC461D-6EED-494A-8B1A-7677A2E7D027",
5324 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
5325 "muted" : false,
5326 "newShot" : false,
5327 "speed" : 1,
5328 "srcIn" : 1449.385515075561,
5329 "start" : 440.40677966101697,
5330 "track" : "v4"
5331 },
5332 {
5333 "duration" : 6.677966101694949,
5334 "fadeIn" : 0,
5335 "fadeOut" : 0,
5336 "id" : "559361D5-9DB2-4B63-8E39-79DEB5DA658A",
5337 "kind" : "video",
5338 "linkId" : "F4E6AD2E-D33A-4B9D-9CC7-E85C4DF5F3C7",
5339 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
5340 "muted" : false,
5341 "newShot" : false,
5342 "speed" : 1,
5343 "srcIn" : 1462.64406779661,
5344 "start" : 449.864406779661,
5345 "track" : "v0"
5346 },
5347 {
5348 "duration" : 6.677966101694949,
5349 "fadeIn" : 0,
5350 "fadeOut" : 0,
5351 "id" : "37884883-822C-45CA-B6E1-133027A1718A",
5352 "kind" : "audio",
5353 "linkId" : "F4E6AD2E-D33A-4B9D-9CC7-E85C4DF5F3C7",
5354 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
5355 "muted" : true,
5356 "newShot" : false,
5357 "speed" : 1,
5358 "srcIn" : 1462.560196255269,
5359 "start" : 449.864406779661,
5360 "track" : "v1"
5361 },
5362 {
5363 "duration" : 6.677966101694949,
5364 "fadeIn" : 0,
5365 "fadeOut" : 0,
5366 "id" : "81675B52-F6A6-4C8A-997F-8AF4803A89E7",
5367 "kind" : "audio",
5368 "linkId" : "F4E6AD2E-D33A-4B9D-9CC7-E85C4DF5F3C7",
5369 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
5370 "muted" : false,
5371 "newShot" : false,
5372 "speed" : 1,
5373 "srcIn" : 1462.6068725584998,
5374 "start" : 449.864406779661,
5375 "track" : "v2"
5376 },
5377 {
5378 "duration" : 6.677966101694949,
5379 "fadeIn" : 0,
5380 "fadeOut" : 0,
5381 "id" : "4787230C-8C29-4CF7-854E-A3AB00759466",
5382 "kind" : "video",
5383 "linkId" : "F4E6AD2E-D33A-4B9D-9CC7-E85C4DF5F3C7",
5384 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
5385 "muted" : false,
5386 "newShot" : false,
5387 "speed" : 1,
5388 "srcIn" : 1462.4702654222717,
5389 "start" : 449.864406779661,
5390 "track" : "v3"
5391 },
5392 {
5393 "duration" : 6.677966101694949,
5394 "fadeIn" : 0,
5395 "fadeOut" : 0,
5396 "id" : "CDA54502-AAC3-496A-B251-B43F1F744E11",
5397 "kind" : "video",
5398 "linkId" : "F4E6AD2E-D33A-4B9D-9CC7-E85C4DF5F3C7",
5399 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
5400 "muted" : false,
5401 "newShot" : false,
5402 "speed" : 1,
5403 "srcIn" : 1462.470260838273,
5404 "start" : 449.864406779661,
5405 "track" : "v4"
5406 },
5407 {
5408 "duration" : 2.1016949152541997,
5409 "fadeIn" : 0,
5410 "fadeOut" : 0,
5411 "id" : "CEDDFF86-9747-4058-98DC-1EB7A4B1F572",
5412 "kind" : "video",
5413 "linkId" : "EDBE29AE-0AD9-452E-A93A-771CE2BE813D",
5414 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
5415 "muted" : false,
5416 "newShot" : false,
5417 "speed" : 1,
5418 "srcIn" : 1471.6610169491523,
5419 "start" : 456.54237288135596,
5420 "track" : "v0"
5421 },
5422 {
5423 "duration" : 2.1016949152541997,
5424 "fadeIn" : 0,
5425 "fadeOut" : 0,
5426 "id" : "65DBD942-E0B7-428A-B955-F7C74957C7E7",
5427 "kind" : "audio",
5428 "linkId" : "EDBE29AE-0AD9-452E-A93A-771CE2BE813D",
5429 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
5430 "muted" : true,
5431 "newShot" : false,
5432 "speed" : 1,
5433 "srcIn" : 1471.5771454078113,
5434 "start" : 456.54237288135596,
5435 "track" : "v1"
5436 },
5437 {
5438 "duration" : 2.1016949152541997,
5439 "fadeIn" : 0,
5440 "fadeOut" : 0,
5441 "id" : "F6898171-8F65-41C6-8518-DFF6DBBABC04",
5442 "kind" : "audio",
5443 "linkId" : "EDBE29AE-0AD9-452E-A93A-771CE2BE813D",
5444 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
5445 "muted" : false,
5446 "newShot" : false,
5447 "speed" : 1,
5448 "srcIn" : 1471.6238217110422,
5449 "start" : 456.54237288135596,
5450 "track" : "v2"
5451 },
5452 {
5453 "duration" : 2.1016949152541997,
5454 "fadeIn" : 0,
5455 "fadeOut" : 0,
5456 "id" : "D865CA10-5853-486F-82AD-02B05EE60EDB",
5457 "kind" : "video",
5458 "linkId" : "EDBE29AE-0AD9-452E-A93A-771CE2BE813D",
5459 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
5460 "muted" : false,
5461 "newShot" : false,
5462 "speed" : 1,
5463 "srcIn" : 1471.487214574814,
5464 "start" : 456.54237288135596,
5465 "track" : "v3"
5466 },
5467 {
5468 "duration" : 2.1016949152541997,
5469 "fadeIn" : 0,
5470 "fadeOut" : 0,
5471 "id" : "0616F857-62D2-44F6-A078-21F47E1CD066",
5472 "kind" : "video",
5473 "linkId" : "EDBE29AE-0AD9-452E-A93A-771CE2BE813D",
5474 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
5475 "muted" : false,
5476 "newShot" : false,
5477 "speed" : 1,
5478 "srcIn" : 1471.4872099908152,
5479 "start" : 456.54237288135596,
5480 "track" : "v4"
5481 },
5482 {
5483 "duration" : 8.101694915254257,
5484 "fadeIn" : 0,
5485 "fadeOut" : 0,
5486 "id" : "A734B468-A8AC-4E1B-BBFF-00BCD0D78390",
5487 "kind" : "video",
5488 "linkId" : "E9457961-D7DC-4D55-8C56-3E6D232AC821",
5489 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
5490 "muted" : false,
5491 "newShot" : false,
5492 "speed" : 1,
5493 "srcIn" : 1478.3389830508472,
5494 "start" : 458.64406779661016,
5495 "track" : "v0"
5496 },
5497 {
5498 "duration" : 8.101694915254257,
5499 "fadeIn" : 0,
5500 "fadeOut" : 0,
5501 "id" : "0B521685-7B77-48BB-B145-8FC0FC1B8D14",
5502 "kind" : "audio",
5503 "linkId" : "E9457961-D7DC-4D55-8C56-3E6D232AC821",
5504 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
5505 "muted" : true,
5506 "newShot" : false,
5507 "speed" : 1,
5508 "srcIn" : 1478.2551115095062,
5509 "start" : 458.64406779661016,
5510 "track" : "v1"
5511 },
5512 {
5513 "duration" : 8.101694915254257,
5514 "fadeIn" : 0,
5515 "fadeOut" : 0,
5516 "id" : "B79DCE82-D278-4E64-BF3E-DD1383DE9320",
5517 "kind" : "audio",
5518 "linkId" : "E9457961-D7DC-4D55-8C56-3E6D232AC821",
5519 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
5520 "muted" : false,
5521 "newShot" : false,
5522 "speed" : 1,
5523 "srcIn" : 1478.301787812737,
5524 "start" : 458.64406779661016,
5525 "track" : "v2"
5526 },
5527 {
5528 "duration" : 8.101694915254257,
5529 "fadeIn" : 0,
5530 "fadeOut" : 0,
5531 "id" : "C1373384-5CBC-48DA-AEEA-B1DC0A1277AB",
5532 "kind" : "video",
5533 "linkId" : "E9457961-D7DC-4D55-8C56-3E6D232AC821",
5534 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
5535 "muted" : false,
5536 "newShot" : false,
5537 "speed" : 1,
5538 "srcIn" : 1478.165180676509,
5539 "start" : 458.64406779661016,
5540 "track" : "v3"
5541 },
5542 {
5543 "duration" : 8.101694915254257,
5544 "fadeIn" : 0,
5545 "fadeOut" : 0,
5546 "id" : "8B3DE2A2-8DE0-408E-96C4-669DAE9AD4A9",
5547 "kind" : "video",
5548 "linkId" : "E9457961-D7DC-4D55-8C56-3E6D232AC821",
5549 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
5550 "muted" : false,
5551 "newShot" : false,
5552 "speed" : 1,
5553 "srcIn" : 1478.1651760925101,
5554 "start" : 458.64406779661016,
5555 "track" : "v4"
5556 },
5557 {
5558 "duration" : 2.13559322033899,
5559 "fadeIn" : 0,
5560 "fadeOut" : 0,
5561 "id" : "EFA97D54-119E-4829-9D03-E931F198D283",
5562 "kind" : "video",
5563 "linkId" : "8FAC32E3-47A3-49EC-A6FC-51E8FD39E2C8",
5564 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
5565 "muted" : false,
5566 "newShot" : false,
5567 "speed" : 1,
5568 "srcIn" : 1491.2203389830506,
5569 "start" : 466.7457627118644,
5570 "track" : "v0"
5571 },
5572 {
5573 "duration" : 2.13559322033899,
5574 "fadeIn" : 0,
5575 "fadeOut" : 0,
5576 "id" : "D10DB1AB-A596-41C4-99D6-2874E01C4666",
5577 "kind" : "audio",
5578 "linkId" : "8FAC32E3-47A3-49EC-A6FC-51E8FD39E2C8",
5579 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
5580 "muted" : true,
5581 "newShot" : false,
5582 "speed" : 1,
5583 "srcIn" : 1491.1364674417096,
5584 "start" : 466.7457627118644,
5585 "track" : "v1"
5586 },
5587 {
5588 "duration" : 2.13559322033899,
5589 "fadeIn" : 0,
5590 "fadeOut" : 0,
5591 "id" : "05FD9DBC-666B-4F86-98C5-B006D88468C8",
5592 "kind" : "audio",
5593 "linkId" : "8FAC32E3-47A3-49EC-A6FC-51E8FD39E2C8",
5594 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
5595 "muted" : false,
5596 "newShot" : false,
5597 "speed" : 1,
5598 "srcIn" : 1491.1831437449405,
5599 "start" : 466.7457627118644,
5600 "track" : "v2"
5601 },
5602 {
5603 "duration" : 2.13559322033899,
5604 "fadeIn" : 0,
5605 "fadeOut" : 0,
5606 "id" : "F8379F4E-716C-4AB7-A712-E90386ACAAF5",
5607 "kind" : "video",
5608 "linkId" : "8FAC32E3-47A3-49EC-A6FC-51E8FD39E2C8",
5609 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
5610 "muted" : false,
5611 "newShot" : false,
5612 "speed" : 1,
5613 "srcIn" : 1491.0465366087124,
5614 "start" : 466.7457627118644,
5615 "track" : "v3"
5616 },
5617 {
5618 "duration" : 2.13559322033899,
5619 "fadeIn" : 0,
5620 "fadeOut" : 0,
5621 "id" : "0AE01210-BDC5-4A27-A1D9-447FB98CBD8C",
5622 "kind" : "video",
5623 "linkId" : "8FAC32E3-47A3-49EC-A6FC-51E8FD39E2C8",
5624 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
5625 "muted" : false,
5626 "newShot" : false,
5627 "speed" : 1,
5628 "srcIn" : 1491.0465320247135,
5629 "start" : 466.7457627118644,
5630 "track" : "v4"
5631 },
5632 {
5633 "duration" : 12.13559322033899,
5634 "fadeIn" : 0,
5635 "fadeOut" : 0,
5636 "id" : "17C94894-CE9A-414A-99D9-983680DBF3C0",
5637 "kind" : "video",
5638 "linkId" : "BD0AA767-5E48-4364-B00E-41745729A942",
5639 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
5640 "muted" : false,
5641 "newShot" : false,
5642 "speed" : 1,
5643 "srcIn" : 1493.3559322033896,
5644 "start" : 468.8813559322034,
5645 "track" : "v0"
5646 },
5647 {
5648 "duration" : 12.13559322033899,
5649 "fadeIn" : 0,
5650 "fadeOut" : 0,
5651 "id" : "517FEEE5-722F-4AF0-9739-D6C4CBFF13F7",
5652 "kind" : "audio",
5653 "linkId" : "BD0AA767-5E48-4364-B00E-41745729A942",
5654 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
5655 "muted" : true,
5656 "newShot" : false,
5657 "speed" : 1,
5658 "srcIn" : 1493.2720606620485,
5659 "start" : 468.8813559322034,
5660 "track" : "v1"
5661 },
5662 {
5663 "duration" : 12.13559322033899,
5664 "fadeIn" : 0,
5665 "fadeOut" : 0,
5666 "id" : "66CDAA10-44F3-4B54-AE2E-76150ACC9CD0",
5667 "kind" : "audio",
5668 "linkId" : "BD0AA767-5E48-4364-B00E-41745729A942",
5669 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
5670 "muted" : false,
5671 "newShot" : false,
5672 "speed" : 1,
5673 "srcIn" : 1493.3187369652794,
5674 "start" : 468.8813559322034,
5675 "track" : "v2"
5676 },
5677 {
5678 "duration" : 12.13559322033899,
5679 "fadeIn" : 0,
5680 "fadeOut" : 0,
5681 "id" : "A6487CCD-4DF5-4DD5-BFE5-2536683FBC19",
5682 "kind" : "video",
5683 "linkId" : "BD0AA767-5E48-4364-B00E-41745729A942",
5684 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
5685 "muted" : false,
5686 "newShot" : false,
5687 "speed" : 1,
5688 "srcIn" : 1493.1821298290513,
5689 "start" : 468.8813559322034,
5690 "track" : "v3"
5691 },
5692 {
5693 "duration" : 12.13559322033899,
5694 "fadeIn" : 0,
5695 "fadeOut" : 0,
5696 "id" : "3E4AB4B8-236F-4408-8452-95DFA2ED6598",
5697 "kind" : "video",
5698 "linkId" : "BD0AA767-5E48-4364-B00E-41745729A942",
5699 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
5700 "muted" : false,
5701 "newShot" : false,
5702 "speed" : 1,
5703 "srcIn" : 1493.1821252450525,
5704 "start" : 468.8813559322034,
5705 "track" : "v4"
5706 },
5707 {
5708 "duration" : 2.7796610169491487,
5709 "fadeIn" : 0,
5710 "fadeOut" : 0,
5711 "id" : "D2B2B51E-C0F9-43FA-A96C-FF04E0956544",
5712 "kind" : "video",
5713 "linkId" : "F6AD378F-5CCF-4E64-988D-88B2876624B9",
5714 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
5715 "muted" : false,
5716 "newShot" : false,
5717 "speed" : 1,
5718 "srcIn" : 1537.8644067796606,
5719 "start" : 481.0169491525424,
5720 "track" : "v0"
5721 },
5722 {
5723 "duration" : 2.7796610169491487,
5724 "fadeIn" : 0,
5725 "fadeOut" : 0,
5726 "id" : "6F365D36-DA75-4DAC-875E-2799D7405B79",
5727 "kind" : "audio",
5728 "linkId" : "F6AD378F-5CCF-4E64-988D-88B2876624B9",
5729 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
5730 "muted" : true,
5731 "newShot" : false,
5732 "speed" : 1,
5733 "srcIn" : 1537.7805352383195,
5734 "start" : 481.0169491525424,
5735 "track" : "v1"
5736 },
5737 {
5738 "duration" : 2.7796610169491487,
5739 "fadeIn" : 0,
5740 "fadeOut" : 0,
5741 "id" : "6086A913-26DD-4E7A-8DA8-5B7FF6C55700",
5742 "kind" : "audio",
5743 "linkId" : "F6AD378F-5CCF-4E64-988D-88B2876624B9",
5744 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
5745 "muted" : false,
5746 "newShot" : false,
5747 "speed" : 1,
5748 "srcIn" : 1537.8272115415505,
5749 "start" : 481.0169491525424,
5750 "track" : "v2"
5751 },
5752 {
5753 "duration" : 2.7796610169491487,
5754 "fadeIn" : 0,
5755 "fadeOut" : 0,
5756 "id" : "2F29A3B0-A10F-4DB4-B9E2-B76DBFA73359",
5757 "kind" : "video",
5758 "linkId" : "F6AD378F-5CCF-4E64-988D-88B2876624B9",
5759 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
5760 "muted" : false,
5761 "newShot" : false,
5762 "speed" : 1,
5763 "srcIn" : 1537.6906044053223,
5764 "start" : 481.0169491525424,
5765 "track" : "v3"
5766 },
5767 {
5768 "duration" : 2.7796610169491487,
5769 "fadeIn" : 0,
5770 "fadeOut" : 0,
5771 "id" : "1684BBB7-47B4-4BFA-B8C1-058C3FAC9CED",
5772 "kind" : "video",
5773 "linkId" : "F6AD378F-5CCF-4E64-988D-88B2876624B9",
5774 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
5775 "muted" : false,
5776 "newShot" : false,
5777 "speed" : 1,
5778 "srcIn" : 1537.6905998213235,
5779 "start" : 481.0169491525424,
5780 "track" : "v4"
5781 },
5782 {
5783 "duration" : 1.0847457627118615,
5784 "fadeIn" : 0,
5785 "fadeOut" : 0,
5786 "id" : "A7680D68-6BCD-4F79-A28F-858D96D4A6E2",
5787 "kind" : "video",
5788 "linkId" : "A3087F67-EF84-47FF-B3BE-7F42F50669A0",
5789 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
5790 "muted" : false,
5791 "newShot" : false,
5792 "speed" : 1,
5793 "srcIn" : 1540.6440677966098,
5794 "start" : 483.79661016949154,
5795 "track" : "v0"
5796 },
5797 {
5798 "duration" : 1.0847457627118615,
5799 "fadeIn" : 0,
5800 "fadeOut" : 0,
5801 "id" : "3854E595-101A-4295-A1B3-5765E651A0AF",
5802 "kind" : "audio",
5803 "linkId" : "A3087F67-EF84-47FF-B3BE-7F42F50669A0",
5804 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
5805 "muted" : true,
5806 "newShot" : false,
5807 "speed" : 1,
5808 "srcIn" : 1540.5601962552687,
5809 "start" : 483.79661016949154,
5810 "track" : "v1"
5811 },
5812 {
5813 "duration" : 1.0847457627118615,
5814 "fadeIn" : 0,
5815 "fadeOut" : 0,
5816 "id" : "D256891E-1C5E-4D56-9CA9-3638EDDF53E4",
5817 "kind" : "audio",
5818 "linkId" : "A3087F67-EF84-47FF-B3BE-7F42F50669A0",
5819 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
5820 "muted" : false,
5821 "newShot" : false,
5822 "speed" : 1,
5823 "srcIn" : 1540.6068725584996,
5824 "start" : 483.79661016949154,
5825 "track" : "v2"
5826 },
5827 {
5828 "duration" : 1.0847457627118615,
5829 "fadeIn" : 0,
5830 "fadeOut" : 0,
5831 "id" : "B12D34EF-C8C7-49A2-8C6F-04B3A3068BC7",
5832 "kind" : "video",
5833 "linkId" : "A3087F67-EF84-47FF-B3BE-7F42F50669A0",
5834 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
5835 "muted" : false,
5836 "newShot" : false,
5837 "speed" : 1,
5838 "srcIn" : 1540.4702654222715,
5839 "start" : 483.79661016949154,
5840 "track" : "v3"
5841 },
5842 {
5843 "duration" : 1.0847457627118615,
5844 "fadeIn" : 0,
5845 "fadeOut" : 0,
5846 "id" : "D07A239B-1D03-40C0-82F5-BF4EE984E137",
5847 "kind" : "video",
5848 "linkId" : "A3087F67-EF84-47FF-B3BE-7F42F50669A0",
5849 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
5850 "muted" : false,
5851 "newShot" : false,
5852 "speed" : 1,
5853 "srcIn" : 1540.4702608382727,
5854 "start" : 483.79661016949154,
5855 "track" : "v4"
5856 },
5857 {
5858 "duration" : 3.5932203389830306,
5859 "fadeIn" : 0,
5860 "fadeOut" : 0,
5861 "id" : "B7BE7F5F-53C9-40B3-9F12-7DF7A2D9685A",
5862 "kind" : "video",
5863 "linkId" : "F9AD2997-81A6-4EF6-BD27-5B43EE87F75A",
5864 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
5865 "muted" : false,
5866 "newShot" : false,
5867 "speed" : 1,
5868 "srcIn" : 1630.745762711864,
5869 "start" : 484.8813559322034,
5870 "track" : "v0"
5871 },
5872 {
5873 "duration" : 3.5932203389830306,
5874 "fadeIn" : 0,
5875 "fadeOut" : 0,
5876 "id" : "B0F7D729-54ED-422A-B202-11DF2760B5F3",
5877 "kind" : "audio",
5878 "linkId" : "F9AD2997-81A6-4EF6-BD27-5B43EE87F75A",
5879 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
5880 "muted" : true,
5881 "newShot" : false,
5882 "speed" : 1,
5883 "srcIn" : 1630.661891170523,
5884 "start" : 484.8813559322034,
5885 "track" : "v1"
5886 },
5887 {
5888 "duration" : 3.5932203389830306,
5889 "fadeIn" : 0,
5890 "fadeOut" : 0,
5891 "id" : "905A3ACC-296C-4843-B9D9-B86F50F5BA5F",
5892 "kind" : "audio",
5893 "linkId" : "F9AD2997-81A6-4EF6-BD27-5B43EE87F75A",
5894 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
5895 "muted" : false,
5896 "newShot" : false,
5897 "speed" : 1,
5898 "srcIn" : 1630.7085674737539,
5899 "start" : 484.8813559322034,
5900 "track" : "v2"
5901 },
5902 {
5903 "duration" : 3.5932203389830306,
5904 "fadeIn" : 0,
5905 "fadeOut" : 0,
5906 "id" : "0B7D26CE-983C-4362-B1CD-AC3A40378E3B",
5907 "kind" : "video",
5908 "linkId" : "F9AD2997-81A6-4EF6-BD27-5B43EE87F75A",
5909 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
5910 "muted" : false,
5911 "newShot" : false,
5912 "speed" : 1,
5913 "srcIn" : 1630.5719603375258,
5914 "start" : 484.8813559322034,
5915 "track" : "v3"
5916 },
5917 {
5918 "duration" : 3.5932203389830306,
5919 "fadeIn" : 0,
5920 "fadeOut" : 0,
5921 "id" : "AFE6D697-39D5-420E-BD1B-A64E0A13DC41",
5922 "kind" : "video",
5923 "linkId" : "F9AD2997-81A6-4EF6-BD27-5B43EE87F75A",
5924 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
5925 "muted" : false,
5926 "newShot" : false,
5927 "speed" : 1,
5928 "srcIn" : 1630.571955753527,
5929 "start" : 484.8813559322034,
5930 "track" : "v4"
5931 },
5932 {
5933 "duration" : 5.525423728813564,
5934 "fadeIn" : 0,
5935 "fadeOut" : 0,
5936 "id" : "FFF0A0F3-132A-4462-A828-0FAEB88BBC4D",
5937 "kind" : "video",
5938 "linkId" : "0DB5CF0D-9192-4B84-94D8-6B5373F51616",
5939 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
5940 "muted" : false,
5941 "newShot" : false,
5942 "speed" : 1,
5943 "srcIn" : 1690.881355932203,
5944 "start" : 488.47457627118644,
5945 "track" : "v0"
5946 },
5947 {
5948 "duration" : 5.525423728813564,
5949 "fadeIn" : 0,
5950 "fadeOut" : 0,
5951 "id" : "8C1B2FDC-C8A5-44ED-9F1E-CA222B045C32",
5952 "kind" : "audio",
5953 "linkId" : "0DB5CF0D-9192-4B84-94D8-6B5373F51616",
5954 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
5955 "muted" : true,
5956 "newShot" : false,
5957 "speed" : 1,
5958 "srcIn" : 1690.7974843908619,
5959 "start" : 488.47457627118644,
5960 "track" : "v1"
5961 },
5962 {
5963 "duration" : 5.525423728813564,
5964 "fadeIn" : 0,
5965 "fadeOut" : 0,
5966 "id" : "630C64EC-19AC-4FC2-B36B-5B8934D80D33",
5967 "kind" : "audio",
5968 "linkId" : "0DB5CF0D-9192-4B84-94D8-6B5373F51616",
5969 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
5970 "muted" : false,
5971 "newShot" : false,
5972 "speed" : 1,
5973 "srcIn" : 1690.8441606940928,
5974 "start" : 488.47457627118644,
5975 "track" : "v2"
5976 },
5977 {
5978 "duration" : 5.525423728813564,
5979 "fadeIn" : 0,
5980 "fadeOut" : 0,
5981 "id" : "234A86B0-ECCC-4314-9B52-A58DB3C619BB",
5982 "kind" : "video",
5983 "linkId" : "0DB5CF0D-9192-4B84-94D8-6B5373F51616",
5984 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
5985 "muted" : false,
5986 "newShot" : false,
5987 "speed" : 1,
5988 "srcIn" : 1690.7075535578647,
5989 "start" : 488.47457627118644,
5990 "track" : "v3"
5991 },
5992 {
5993 "duration" : 5.525423728813564,
5994 "fadeIn" : 0,
5995 "fadeOut" : 0,
5996 "id" : "02321509-9B22-45BA-884A-BFB975074D08",
5997 "kind" : "video",
5998 "linkId" : "0DB5CF0D-9192-4B84-94D8-6B5373F51616",
5999 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
6000 "muted" : false,
6001 "newShot" : false,
6002 "speed" : 1,
6003 "srcIn" : 1690.7075489738659,
6004 "start" : 488.47457627118644,
6005 "track" : "v4"
6006 },
6007 {
6008 "duration" : 3.9661016949152668,
6009 "fadeIn" : 0,
6010 "fadeOut" : 0,
6011 "id" : "A8D392D7-E60E-4189-B7D6-B1C2B5E6927D",
6012 "kind" : "video",
6013 "linkId" : "A50B7D21-1369-498E-A893-10D9266DA682",
6014 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
6015 "muted" : false,
6016 "newShot" : false,
6017 "speed" : 1,
6018 "srcIn" : 1703.5254237288132,
6019 "start" : 494,
6020 "track" : "v0"
6021 },
6022 {
6023 "duration" : 3.9661016949152668,
6024 "fadeIn" : 0,
6025 "fadeOut" : 0,
6026 "id" : "79B92814-E4D4-422A-B414-FEC1D42BABF8",
6027 "kind" : "audio",
6028 "linkId" : "A50B7D21-1369-498E-A893-10D9266DA682",
6029 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
6030 "muted" : true,
6031 "newShot" : false,
6032 "speed" : 1,
6033 "srcIn" : 1703.441552187472,
6034 "start" : 494,
6035 "track" : "v1"
6036 },
6037 {
6038 "duration" : 3.9661016949152668,
6039 "fadeIn" : 0,
6040 "fadeOut" : 0,
6041 "id" : "154F32D0-7DAB-433B-833A-86D9F7B2B99E",
6042 "kind" : "audio",
6043 "linkId" : "A50B7D21-1369-498E-A893-10D9266DA682",
6044 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
6045 "muted" : false,
6046 "newShot" : false,
6047 "speed" : 1,
6048 "srcIn" : 1703.488228490703,
6049 "start" : 494,
6050 "track" : "v2"
6051 },
6052 {
6053 "duration" : 3.9661016949152668,
6054 "fadeIn" : 0,
6055 "fadeOut" : 0,
6056 "id" : "C57B1520-E9CD-43AB-ABB5-AA9F59B97CE5",
6057 "kind" : "video",
6058 "linkId" : "A50B7D21-1369-498E-A893-10D9266DA682",
6059 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
6060 "muted" : false,
6061 "newShot" : false,
6062 "speed" : 1,
6063 "srcIn" : 1703.351621354475,
6064 "start" : 494,
6065 "track" : "v3"
6066 },
6067 {
6068 "duration" : 3.9661016949152668,
6069 "fadeIn" : 0,
6070 "fadeOut" : 0,
6071 "id" : "72ECB107-E625-47A0-91F5-4B07C84F9D01",
6072 "kind" : "video",
6073 "linkId" : "A50B7D21-1369-498E-A893-10D9266DA682",
6074 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
6075 "muted" : false,
6076 "newShot" : false,
6077 "speed" : 1,
6078 "srcIn" : 1703.351616770476,
6079 "start" : 494,
6080 "track" : "v4"
6081 },
6082 {
6083 "duration" : 3.322033898305051,
6084 "fadeIn" : 0,
6085 "fadeOut" : 0,
6086 "id" : "592BF8E8-84BB-4D68-ACAF-C5250A378311",
6087 "kind" : "video",
6088 "linkId" : "2F16524C-A3E5-4EC3-B918-2C0107EB5F71",
6089 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
6090 "muted" : false,
6091 "newShot" : false,
6092 "speed" : 1,
6093 "srcIn" : 1714.4067796610166,
6094 "start" : 497.96610169491527,
6095 "track" : "v0"
6096 },
6097 {
6098 "duration" : 3.322033898305051,
6099 "fadeIn" : 0,
6100 "fadeOut" : 0,
6101 "id" : "6A420F63-349C-490A-AEF2-A9A23B8F5911",
6102 "kind" : "audio",
6103 "linkId" : "2F16524C-A3E5-4EC3-B918-2C0107EB5F71",
6104 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
6105 "muted" : true,
6106 "newShot" : false,
6107 "speed" : 1,
6108 "srcIn" : 1714.3229081196755,
6109 "start" : 497.96610169491527,
6110 "track" : "v1"
6111 },
6112 {
6113 "duration" : 3.322033898305051,
6114 "fadeIn" : 0,
6115 "fadeOut" : 0,
6116 "id" : "9BFFE86E-C803-440C-A23A-27946EB8F38E",
6117 "kind" : "audio",
6118 "linkId" : "2F16524C-A3E5-4EC3-B918-2C0107EB5F71",
6119 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
6120 "muted" : false,
6121 "newShot" : false,
6122 "speed" : 1,
6123 "srcIn" : 1714.3695844229064,
6124 "start" : 497.96610169491527,
6125 "track" : "v2"
6126 },
6127 {
6128 "duration" : 3.322033898305051,
6129 "fadeIn" : 0,
6130 "fadeOut" : 0,
6131 "id" : "507C5A42-E790-48A6-8DE6-F867042AD251",
6132 "kind" : "video",
6133 "linkId" : "2F16524C-A3E5-4EC3-B918-2C0107EB5F71",
6134 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
6135 "muted" : false,
6136 "newShot" : false,
6137 "speed" : 1,
6138 "srcIn" : 1714.2329772866783,
6139 "start" : 497.96610169491527,
6140 "track" : "v3"
6141 },
6142 {
6143 "duration" : 3.322033898305051,
6144 "fadeIn" : 0,
6145 "fadeOut" : 0,
6146 "id" : "1255A12C-C270-451F-B9E7-2C3827620218",
6147 "kind" : "video",
6148 "linkId" : "2F16524C-A3E5-4EC3-B918-2C0107EB5F71",
6149 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
6150 "muted" : false,
6151 "newShot" : false,
6152 "speed" : 1,
6153 "srcIn" : 1714.2329727026795,
6154 "start" : 497.96610169491527,
6155 "track" : "v4"
6156 },
6157 {
6158 "duration" : 5.491525423728831,
6159 "fadeIn" : 0,
6160 "fadeOut" : 0,
6161 "id" : "FAF25F24-F13A-4DE0-9CF8-7D2584531C9E",
6162 "kind" : "video",
6163 "linkId" : "EE183339-F801-4116-A9EF-D64A77C5996D",
6164 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
6165 "muted" : false,
6166 "newShot" : false,
6167 "speed" : 1,
6168 "srcIn" : 1722.372881355932,
6169 "start" : 501.2881355932203,
6170 "track" : "v0"
6171 },
6172 {
6173 "duration" : 5.491525423728831,
6174 "fadeIn" : 0,
6175 "fadeOut" : 0,
6176 "id" : "E764AB44-88DC-41E9-A83E-E32A15AD06C5",
6177 "kind" : "audio",
6178 "linkId" : "EE183339-F801-4116-A9EF-D64A77C5996D",
6179 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
6180 "muted" : true,
6181 "newShot" : false,
6182 "speed" : 1,
6183 "srcIn" : 1722.2890098145908,
6184 "start" : 501.2881355932203,
6185 "track" : "v1"
6186 },
6187 {
6188 "duration" : 5.491525423728831,
6189 "fadeIn" : 0,
6190 "fadeOut" : 0,
6191 "id" : "A156F3B8-0CDE-4ADF-8CA0-BA16EF901769",
6192 "kind" : "audio",
6193 "linkId" : "EE183339-F801-4116-A9EF-D64A77C5996D",
6194 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
6195 "muted" : false,
6196 "newShot" : false,
6197 "speed" : 1,
6198 "srcIn" : 1722.3356861178218,
6199 "start" : 501.2881355932203,
6200 "track" : "v2"
6201 },
6202 {
6203 "duration" : 5.491525423728831,
6204 "fadeIn" : 0,
6205 "fadeOut" : 0,
6206 "id" : "60A808DE-37D0-4C45-925D-276CA1D59EB7",
6207 "kind" : "video",
6208 "linkId" : "EE183339-F801-4116-A9EF-D64A77C5996D",
6209 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
6210 "muted" : false,
6211 "newShot" : false,
6212 "speed" : 1,
6213 "srcIn" : 1722.1990789815936,
6214 "start" : 501.2881355932203,
6215 "track" : "v3"
6216 },
6217 {
6218 "duration" : 5.491525423728831,
6219 "fadeIn" : 0,
6220 "fadeOut" : 0,
6221 "id" : "C8564707-EE02-4CAD-BFA6-0089C454CE2A",
6222 "kind" : "video",
6223 "linkId" : "EE183339-F801-4116-A9EF-D64A77C5996D",
6224 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
6225 "muted" : false,
6226 "newShot" : false,
6227 "speed" : 1,
6228 "srcIn" : 1722.1990743975948,
6229 "start" : 501.2881355932203,
6230 "track" : "v4"
6231 },
6232 {
6233 "duration" : 7.627118644067764,
6234 "fadeIn" : 0,
6235 "fadeOut" : 0,
6236 "id" : "BB4F9A82-C969-4E01-BDBE-E481B17343CF",
6237 "kind" : "video",
6238 "linkId" : "DFDDB477-FE81-42F4-A11E-389F9E0A1439",
6239 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
6240 "muted" : false,
6241 "newShot" : false,
6242 "speed" : 1,
6243 "srcIn" : 1729.050847457627,
6244 "start" : 506.77966101694915,
6245 "track" : "v0"
6246 },
6247 {
6248 "duration" : 7.627118644067764,
6249 "fadeIn" : 0,
6250 "fadeOut" : 0,
6251 "id" : "D539326D-CFC6-4F6B-A59C-770061337CBD",
6252 "kind" : "audio",
6253 "linkId" : "DFDDB477-FE81-42F4-A11E-389F9E0A1439",
6254 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
6255 "muted" : true,
6256 "newShot" : false,
6257 "speed" : 1,
6258 "srcIn" : 1728.966975916286,
6259 "start" : 506.77966101694915,
6260 "track" : "v1"
6261 },
6262 {
6263 "duration" : 7.627118644067764,
6264 "fadeIn" : 0,
6265 "fadeOut" : 0,
6266 "id" : "28F991A4-9D42-4CBC-BE96-E89FEBFAF96C",
6267 "kind" : "audio",
6268 "linkId" : "DFDDB477-FE81-42F4-A11E-389F9E0A1439",
6269 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
6270 "muted" : false,
6271 "newShot" : false,
6272 "speed" : 1,
6273 "srcIn" : 1729.0136522195169,
6274 "start" : 506.77966101694915,
6275 "track" : "v2"
6276 },
6277 {
6278 "duration" : 7.627118644067764,
6279 "fadeIn" : 0,
6280 "fadeOut" : 0,
6281 "id" : "D4D4B878-3981-44A7-9435-B6B607F17D8E",
6282 "kind" : "video",
6283 "linkId" : "DFDDB477-FE81-42F4-A11E-389F9E0A1439",
6284 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
6285 "muted" : false,
6286 "newShot" : false,
6287 "speed" : 1,
6288 "srcIn" : 1728.8770450832887,
6289 "start" : 506.77966101694915,
6290 "track" : "v3"
6291 },
6292 {
6293 "duration" : 7.627118644067764,
6294 "fadeIn" : 0,
6295 "fadeOut" : 0,
6296 "id" : "BFB922A9-8931-4826-A5AE-4A8F51627F00",
6297 "kind" : "video",
6298 "linkId" : "DFDDB477-FE81-42F4-A11E-389F9E0A1439",
6299 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
6300 "muted" : false,
6301 "newShot" : false,
6302 "speed" : 1,
6303 "srcIn" : 1728.87704049929,
6304 "start" : 506.77966101694915,
6305 "track" : "v4"
6306 },
6307 {
6308 "duration" : 7.491525423728831,
6309 "fadeIn" : 0,
6310 "fadeOut" : 0,
6311 "id" : "08360F23-5B06-4769-94DC-42A31CD3BD46",
6312 "kind" : "video",
6313 "linkId" : "2C7554F2-E73F-41CD-85CB-697100DCD6C1",
6314 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
6315 "muted" : false,
6316 "newShot" : false,
6317 "speed" : 1,
6318 "srcIn" : 1747.593220338983,
6319 "start" : 514.4067796610169,
6320 "track" : "v0"
6321 },
6322 {
6323 "duration" : 7.491525423728831,
6324 "fadeIn" : 0,
6325 "fadeOut" : 0,
6326 "id" : "8BCF92B0-D87A-42BB-A168-F889F41CD1AB",
6327 "kind" : "audio",
6328 "linkId" : "2C7554F2-E73F-41CD-85CB-697100DCD6C1",
6329 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
6330 "muted" : true,
6331 "newShot" : false,
6332 "speed" : 1,
6333 "srcIn" : 1747.509348797642,
6334 "start" : 514.4067796610169,
6335 "track" : "v1"
6336 },
6337 {
6338 "duration" : 7.491525423728831,
6339 "fadeIn" : 0,
6340 "fadeOut" : 0,
6341 "id" : "2753AF8E-8B68-4B0D-A289-CCF8ABFA4EA6",
6342 "kind" : "audio",
6343 "linkId" : "2C7554F2-E73F-41CD-85CB-697100DCD6C1",
6344 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
6345 "muted" : false,
6346 "newShot" : false,
6347 "speed" : 1,
6348 "srcIn" : 1747.5560251008728,
6349 "start" : 514.4067796610169,
6350 "track" : "v2"
6351 },
6352 {
6353 "duration" : 7.491525423728831,
6354 "fadeIn" : 0,
6355 "fadeOut" : 0,
6356 "id" : "913D7AB9-49C1-4FEE-B538-D81313C8CDAA",
6357 "kind" : "video",
6358 "linkId" : "2C7554F2-E73F-41CD-85CB-697100DCD6C1",
6359 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
6360 "muted" : false,
6361 "newShot" : false,
6362 "speed" : 1,
6363 "srcIn" : 1747.4194179646447,
6364 "start" : 514.4067796610169,
6365 "track" : "v3"
6366 },
6367 {
6368 "duration" : 7.491525423728831,
6369 "fadeIn" : 0,
6370 "fadeOut" : 0,
6371 "id" : "A4078960-1488-4073-A3C0-10A87E8A55C7",
6372 "kind" : "video",
6373 "linkId" : "2C7554F2-E73F-41CD-85CB-697100DCD6C1",
6374 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
6375 "muted" : false,
6376 "newShot" : false,
6377 "speed" : 1,
6378 "srcIn" : 1747.419413380646,
6379 "start" : 514.4067796610169,
6380 "track" : "v4"
6381 },
6382 {
6383 "duration" : 10.677966101694892,
6384 "fadeIn" : 0,
6385 "fadeOut" : 0,
6386 "id" : "B0A75210-7D43-4E54-906B-32BFB4856787",
6387 "kind" : "video",
6388 "linkId" : "68BA82C2-1D37-4341-BBD5-0BE799A2C6FA",
6389 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
6390 "muted" : false,
6391 "newShot" : false,
6392 "speed" : 1,
6393 "srcIn" : 1755.084745762712,
6394 "start" : 521.8983050847457,
6395 "track" : "v0"
6396 },
6397 {
6398 "duration" : 10.677966101694892,
6399 "fadeIn" : 0,
6400 "fadeOut" : 0,
6401 "id" : "581E6DD4-D543-4188-9C1B-BC9D2F1F908C",
6402 "kind" : "audio",
6403 "linkId" : "68BA82C2-1D37-4341-BBD5-0BE799A2C6FA",
6404 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
6405 "muted" : true,
6406 "newShot" : false,
6407 "speed" : 1,
6408 "srcIn" : 1755.0008742213709,
6409 "start" : 521.8983050847457,
6410 "track" : "v1"
6411 },
6412 {
6413 "duration" : 10.677966101694892,
6414 "fadeIn" : 0,
6415 "fadeOut" : 0,
6416 "id" : "B8BFF2A6-3F9E-4D5E-8DFE-EDF5101E1AE5",
6417 "kind" : "audio",
6418 "linkId" : "68BA82C2-1D37-4341-BBD5-0BE799A2C6FA",
6419 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
6420 "muted" : false,
6421 "newShot" : false,
6422 "speed" : 1,
6423 "srcIn" : 1755.0475505246018,
6424 "start" : 521.8983050847457,
6425 "track" : "v2"
6426 },
6427 {
6428 "duration" : 10.677966101694892,
6429 "fadeIn" : 0,
6430 "fadeOut" : 0,
6431 "id" : "6669C09D-E39D-46DE-A862-6BA3B20F558E",
6432 "kind" : "video",
6433 "linkId" : "68BA82C2-1D37-4341-BBD5-0BE799A2C6FA",
6434 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
6435 "muted" : false,
6436 "newShot" : false,
6437 "speed" : 1,
6438 "srcIn" : 1754.9109433883737,
6439 "start" : 521.8983050847457,
6440 "track" : "v3"
6441 },
6442 {
6443 "duration" : 10.677966101694892,
6444 "fadeIn" : 0,
6445 "fadeOut" : 0,
6446 "id" : "C6619902-8ED4-46F4-907C-4F6DF342850B",
6447 "kind" : "video",
6448 "linkId" : "68BA82C2-1D37-4341-BBD5-0BE799A2C6FA",
6449 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
6450 "muted" : false,
6451 "newShot" : false,
6452 "speed" : 1,
6453 "srcIn" : 1754.9109388043748,
6454 "start" : 521.8983050847457,
6455 "track" : "v4"
6456 },
6457 {
6458 "duration" : 3.152542372881385,
6459 "fadeIn" : 0,
6460 "fadeOut" : 0,
6461 "id" : "1E1F6B29-A2BC-44CA-BC6B-B2DA54F93FE5",
6462 "kind" : "video",
6463 "linkId" : "F2447BC6-DF74-439E-8FD2-0FBAC39A205C",
6464 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
6465 "muted" : false,
6466 "newShot" : false,
6467 "speed" : 1,
6468 "srcIn" : 1766.6101694915255,
6469 "start" : 532.5762711864406,
6470 "track" : "v0"
6471 },
6472 {
6473 "duration" : 3.152542372881385,
6474 "fadeIn" : 0,
6475 "fadeOut" : 0,
6476 "id" : "97A49AEA-492D-4D8E-A171-E4F0831755E7",
6477 "kind" : "audio",
6478 "linkId" : "F2447BC6-DF74-439E-8FD2-0FBAC39A205C",
6479 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
6480 "muted" : true,
6481 "newShot" : false,
6482 "speed" : 1,
6483 "srcIn" : 1766.5262979501845,
6484 "start" : 532.5762711864406,
6485 "track" : "v1"
6486 },
6487 {
6488 "duration" : 3.152542372881385,
6489 "fadeIn" : 0,
6490 "fadeOut" : 0,
6491 "id" : "C0542DA4-D2C3-4854-B510-3701574E9CD1",
6492 "kind" : "audio",
6493 "linkId" : "F2447BC6-DF74-439E-8FD2-0FBAC39A205C",
6494 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
6495 "muted" : false,
6496 "newShot" : false,
6497 "speed" : 1,
6498 "srcIn" : 1766.5729742534154,
6499 "start" : 532.5762711864406,
6500 "track" : "v2"
6501 },
6502 {
6503 "duration" : 3.152542372881385,
6504 "fadeIn" : 0,
6505 "fadeOut" : 0,
6506 "id" : "85D95CB3-B6F4-4DFA-9F00-55E80014D2B2",
6507 "kind" : "video",
6508 "linkId" : "F2447BC6-DF74-439E-8FD2-0FBAC39A205C",
6509 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
6510 "muted" : false,
6511 "newShot" : false,
6512 "speed" : 1,
6513 "srcIn" : 1766.4363671171873,
6514 "start" : 532.5762711864406,
6515 "track" : "v3"
6516 },
6517 {
6518 "duration" : 3.152542372881385,
6519 "fadeIn" : 0,
6520 "fadeOut" : 0,
6521 "id" : "3AD412AE-C961-4FE7-B65F-3266A0980B36",
6522 "kind" : "video",
6523 "linkId" : "F2447BC6-DF74-439E-8FD2-0FBAC39A205C",
6524 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
6525 "muted" : false,
6526 "newShot" : false,
6527 "speed" : 1,
6528 "srcIn" : 1766.4363625331885,
6529 "start" : 532.5762711864406,
6530 "track" : "v4"
6531 },
6532 {
6533 "duration" : 5.491525423728831,
6534 "fadeIn" : 0,
6535 "fadeOut" : 0,
6536 "id" : "1F116DEE-E2FC-4C19-B84E-F62E8C7FB58E",
6537 "kind" : "video",
6538 "linkId" : "CC83DD13-876C-4795-A07C-8A9F20E0FCD4",
6539 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
6540 "muted" : false,
6541 "newShot" : false,
6542 "speed" : 1,
6543 "srcIn" : 1771.898305084746,
6544 "start" : 535.728813559322,
6545 "track" : "v0"
6546 },
6547 {
6548 "duration" : 5.491525423728831,
6549 "fadeIn" : 0,
6550 "fadeOut" : 0,
6551 "id" : "26F01770-1063-4614-838E-9C7408CB8EA7",
6552 "kind" : "audio",
6553 "linkId" : "CC83DD13-876C-4795-A07C-8A9F20E0FCD4",
6554 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
6555 "muted" : true,
6556 "newShot" : false,
6557 "speed" : 1,
6558 "srcIn" : 1771.814433543405,
6559 "start" : 535.728813559322,
6560 "track" : "v1"
6561 },
6562 {
6563 "duration" : 5.491525423728831,
6564 "fadeIn" : 0,
6565 "fadeOut" : 0,
6566 "id" : "E27B6F72-E807-4B80-B045-EF94CBEAE518",
6567 "kind" : "audio",
6568 "linkId" : "CC83DD13-876C-4795-A07C-8A9F20E0FCD4",
6569 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
6570 "muted" : false,
6571 "newShot" : false,
6572 "speed" : 1,
6573 "srcIn" : 1771.8611098466358,
6574 "start" : 535.728813559322,
6575 "track" : "v2"
6576 },
6577 {
6578 "duration" : 5.491525423728831,
6579 "fadeIn" : 0,
6580 "fadeOut" : 0,
6581 "id" : "780E91E1-D956-46CE-AD94-132F9146FF16",
6582 "kind" : "video",
6583 "linkId" : "CC83DD13-876C-4795-A07C-8A9F20E0FCD4",
6584 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
6585 "muted" : false,
6586 "newShot" : false,
6587 "speed" : 1,
6588 "srcIn" : 1771.7245027104077,
6589 "start" : 535.728813559322,
6590 "track" : "v3"
6591 },
6592 {
6593 "duration" : 5.491525423728831,
6594 "fadeIn" : 0,
6595 "fadeOut" : 0,
6596 "id" : "502017FB-B15D-472A-92CD-864B01FA9D5A",
6597 "kind" : "video",
6598 "linkId" : "CC83DD13-876C-4795-A07C-8A9F20E0FCD4",
6599 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
6600 "muted" : false,
6601 "newShot" : false,
6602 "speed" : 1,
6603 "srcIn" : 1771.724498126409,
6604 "start" : 535.728813559322,
6605 "track" : "v4"
6606 },
6607 {
6608 "duration" : 4.0677966101694665,
6609 "fadeIn" : 0,
6610 "fadeOut" : 0,
6611 "id" : "C08F7E72-3AD4-4B1D-8250-1512128E7940",
6612 "kind" : "video",
6613 "linkId" : "A2118321-9E02-4656-BCE9-0E3980E8372E",
6614 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
6615 "muted" : false,
6616 "newShot" : false,
6617 "speed" : 1,
6618 "srcIn" : 1779.5593220338988,
6619 "start" : 541.2203389830509,
6620 "track" : "v0"
6621 },
6622 {
6623 "duration" : 4.0677966101694665,
6624 "fadeIn" : 0,
6625 "fadeOut" : 0,
6626 "id" : "B58ABDEF-6C61-45A8-95FF-6445D0C0EABC",
6627 "kind" : "audio",
6628 "linkId" : "A2118321-9E02-4656-BCE9-0E3980E8372E",
6629 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
6630 "muted" : true,
6631 "newShot" : false,
6632 "speed" : 1,
6633 "srcIn" : 1779.4754504925577,
6634 "start" : 541.2203389830509,
6635 "track" : "v1"
6636 },
6637 {
6638 "duration" : 4.0677966101694665,
6639 "fadeIn" : 0,
6640 "fadeOut" : 0,
6641 "id" : "141D7901-0677-44F5-8296-15BC113C1EBE",
6642 "kind" : "audio",
6643 "linkId" : "A2118321-9E02-4656-BCE9-0E3980E8372E",
6644 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
6645 "muted" : false,
6646 "newShot" : false,
6647 "speed" : 1,
6648 "srcIn" : 1779.5221267957886,
6649 "start" : 541.2203389830509,
6650 "track" : "v2"
6651 },
6652 {
6653 "duration" : 4.0677966101694665,
6654 "fadeIn" : 0,
6655 "fadeOut" : 0,
6656 "id" : "BADB3830-CF8B-4208-A04F-439958ACADDA",
6657 "kind" : "video",
6658 "linkId" : "A2118321-9E02-4656-BCE9-0E3980E8372E",
6659 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
6660 "muted" : false,
6661 "newShot" : false,
6662 "speed" : 1,
6663 "srcIn" : 1779.3855196595605,
6664 "start" : 541.2203389830509,
6665 "track" : "v3"
6666 },
6667 {
6668 "duration" : 4.0677966101694665,
6669 "fadeIn" : 0,
6670 "fadeOut" : 0,
6671 "id" : "83BD9C59-2FC5-4F64-9963-A86B647F6F52",
6672 "kind" : "video",
6673 "linkId" : "A2118321-9E02-4656-BCE9-0E3980E8372E",
6674 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
6675 "muted" : false,
6676 "newShot" : false,
6677 "speed" : 1,
6678 "srcIn" : 1779.3855150755617,
6679 "start" : 541.2203389830509,
6680 "track" : "v4"
6681 },
6682 {
6683 "duration" : 1.661016949152554,
6684 "fadeIn" : 0,
6685 "fadeOut" : 0,
6686 "id" : "81042DC7-94FE-4359-959F-C9E29D4F9461",
6687 "kind" : "video",
6688 "linkId" : "CDF46AA7-2655-4344-81E6-6C7AB4F4B303",
6689 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
6690 "muted" : false,
6691 "newShot" : false,
6692 "speed" : 1,
6693 "srcIn" : 1815.5932203389834,
6694 "start" : 545.2881355932203,
6695 "track" : "v0"
6696 },
6697 {
6698 "duration" : 1.661016949152554,
6699 "fadeIn" : 0,
6700 "fadeOut" : 0,
6701 "id" : "EB5D9E21-E9FF-430D-B90A-143FFFC161D0",
6702 "kind" : "audio",
6703 "linkId" : "CDF46AA7-2655-4344-81E6-6C7AB4F4B303",
6704 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
6705 "muted" : true,
6706 "newShot" : false,
6707 "speed" : 1,
6708 "srcIn" : 1815.5093487976424,
6709 "start" : 545.2881355932203,
6710 "track" : "v1"
6711 },
6712 {
6713 "duration" : 1.661016949152554,
6714 "fadeIn" : 0,
6715 "fadeOut" : 0,
6716 "id" : "E5C8677F-212F-43D4-95D6-F86D218562F5",
6717 "kind" : "audio",
6718 "linkId" : "CDF46AA7-2655-4344-81E6-6C7AB4F4B303",
6719 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
6720 "muted" : false,
6721 "newShot" : false,
6722 "speed" : 1,
6723 "srcIn" : 1815.5560251008733,
6724 "start" : 545.2881355932203,
6725 "track" : "v2"
6726 },
6727 {
6728 "duration" : 1.661016949152554,
6729 "fadeIn" : 0,
6730 "fadeOut" : 0,
6731 "id" : "899CE36B-86FD-4463-ACF9-64EFDF419140",
6732 "kind" : "video",
6733 "linkId" : "CDF46AA7-2655-4344-81E6-6C7AB4F4B303",
6734 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
6735 "muted" : false,
6736 "newShot" : false,
6737 "speed" : 1,
6738 "srcIn" : 1815.4194179646452,
6739 "start" : 545.2881355932203,
6740 "track" : "v3"
6741 },
6742 {
6743 "duration" : 1.661016949152554,
6744 "fadeIn" : 0,
6745 "fadeOut" : 0,
6746 "id" : "B0B393DE-7AAD-46D0-8783-D2E08335DC13",
6747 "kind" : "video",
6748 "linkId" : "CDF46AA7-2655-4344-81E6-6C7AB4F4B303",
6749 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
6750 "muted" : false,
6751 "newShot" : false,
6752 "speed" : 1,
6753 "srcIn" : 1815.4194133806463,
6754 "start" : 545.2881355932203,
6755 "track" : "v4"
6756 },
6757 {
6758 "duration" : 1.1525423728813848,
6759 "fadeIn" : 0,
6760 "fadeOut" : 0,
6761 "id" : "18E9C9F7-AA69-4D86-9DD9-5278392186CD",
6762 "kind" : "video",
6763 "linkId" : "2426D580-BCAA-49B5-BF7C-9DCE2A558605",
6764 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
6765 "muted" : false,
6766 "newShot" : false,
6767 "speed" : 1,
6768 "srcIn" : 1817.5593220338988,
6769 "start" : 546.9491525423729,
6770 "track" : "v0"
6771 },
6772 {
6773 "duration" : 1.1525423728813848,
6774 "fadeIn" : 0,
6775 "fadeOut" : 0,
6776 "id" : "098E9E68-8FCE-4C2E-8A24-81EFEECCD127",
6777 "kind" : "audio",
6778 "linkId" : "2426D580-BCAA-49B5-BF7C-9DCE2A558605",
6779 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
6780 "muted" : true,
6781 "newShot" : false,
6782 "speed" : 1,
6783 "srcIn" : 1817.4754504925577,
6784 "start" : 546.9491525423729,
6785 "track" : "v1"
6786 },
6787 {
6788 "duration" : 1.1525423728813848,
6789 "fadeIn" : 0,
6790 "fadeOut" : 0,
6791 "id" : "3A68B520-1B13-4837-98C7-4715D6250B50",
6792 "kind" : "audio",
6793 "linkId" : "2426D580-BCAA-49B5-BF7C-9DCE2A558605",
6794 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
6795 "muted" : false,
6796 "newShot" : false,
6797 "speed" : 1,
6798 "srcIn" : 1817.5221267957886,
6799 "start" : 546.9491525423729,
6800 "track" : "v2"
6801 },
6802 {
6803 "duration" : 1.1525423728813848,
6804 "fadeIn" : 0,
6805 "fadeOut" : 0,
6806 "id" : "3D51136D-FC09-46D4-A29C-C1E8B7022D04",
6807 "kind" : "video",
6808 "linkId" : "2426D580-BCAA-49B5-BF7C-9DCE2A558605",
6809 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
6810 "muted" : false,
6811 "newShot" : false,
6812 "speed" : 1,
6813 "srcIn" : 1817.3855196595605,
6814 "start" : 546.9491525423729,
6815 "track" : "v3"
6816 },
6817 {
6818 "duration" : 1.1525423728813848,
6819 "fadeIn" : 0,
6820 "fadeOut" : 0,
6821 "id" : "879B5431-78D7-4E34-98CD-811F0D7D8D2B",
6822 "kind" : "video",
6823 "linkId" : "2426D580-BCAA-49B5-BF7C-9DCE2A558605",
6824 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
6825 "muted" : false,
6826 "newShot" : false,
6827 "speed" : 1,
6828 "srcIn" : 1817.3855150755617,
6829 "start" : 546.9491525423729,
6830 "track" : "v4"
6831 },
6832 {
6833 "duration" : 11.525423728813507,
6834 "fadeIn" : 0,
6835 "fadeOut" : 0,
6836 "id" : "B6AD6E92-A164-48D3-962D-8D5472AC5ED8",
6837 "kind" : "video",
6838 "linkId" : "5F7B4F1D-CE0A-49F1-A14C-6C7D1809E682",
6839 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
6840 "muted" : false,
6841 "newShot" : false,
6842 "speed" : 1,
6843 "srcIn" : 1823.5593220338988,
6844 "start" : 548.1016949152543,
6845 "track" : "v0"
6846 },
6847 {
6848 "duration" : 11.525423728813507,
6849 "fadeIn" : 0,
6850 "fadeOut" : 0,
6851 "id" : "15310F42-B66D-4DDB-B571-28824E2AA8F2",
6852 "kind" : "audio",
6853 "linkId" : "5F7B4F1D-CE0A-49F1-A14C-6C7D1809E682",
6854 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
6855 "muted" : true,
6856 "newShot" : false,
6857 "speed" : 1,
6858 "srcIn" : 1823.4754504925577,
6859 "start" : 548.1016949152543,
6860 "track" : "v1"
6861 },
6862 {
6863 "duration" : 11.525423728813507,
6864 "fadeIn" : 0,
6865 "fadeOut" : 0,
6866 "id" : "60DB4DB9-D800-414F-8313-D3C0C9CF620C",
6867 "kind" : "audio",
6868 "linkId" : "5F7B4F1D-CE0A-49F1-A14C-6C7D1809E682",
6869 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
6870 "muted" : false,
6871 "newShot" : false,
6872 "speed" : 1,
6873 "srcIn" : 1823.5221267957886,
6874 "start" : 548.1016949152543,
6875 "track" : "v2"
6876 },
6877 {
6878 "duration" : 11.525423728813507,
6879 "fadeIn" : 0,
6880 "fadeOut" : 0,
6881 "id" : "66EBCF41-7ABC-4B4F-B3D6-670B5DE5FF4B",
6882 "kind" : "video",
6883 "linkId" : "5F7B4F1D-CE0A-49F1-A14C-6C7D1809E682",
6884 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
6885 "muted" : false,
6886 "newShot" : false,
6887 "speed" : 1,
6888 "srcIn" : 1823.3855196595605,
6889 "start" : 548.1016949152543,
6890 "track" : "v3"
6891 },
6892 {
6893 "duration" : 11.525423728813507,
6894 "fadeIn" : 0,
6895 "fadeOut" : 0,
6896 "id" : "74B48312-8BD1-4CF9-967A-E7EED8D50E2A",
6897 "kind" : "video",
6898 "linkId" : "5F7B4F1D-CE0A-49F1-A14C-6C7D1809E682",
6899 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
6900 "muted" : false,
6901 "newShot" : false,
6902 "speed" : 1,
6903 "srcIn" : 1823.3855150755617,
6904 "start" : 548.1016949152543,
6905 "track" : "v4"
6906 },
6907 {
6908 "duration" : 3.6271186440678775,
6909 "fadeIn" : 0,
6910 "fadeOut" : 0,
6911 "id" : "7E495919-B008-4616-BB9C-17E3BDE688A9",
6912 "kind" : "video",
6913 "linkId" : "9769E036-22A2-4F7C-95DD-F0F9F243A183",
6914 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
6915 "muted" : false,
6916 "newShot" : false,
6917 "speed" : 1,
6918 "srcIn" : 1836.474576271187,
6919 "start" : 559.6271186440678,
6920 "track" : "v0"
6921 },
6922 {
6923 "duration" : 3.6271186440678775,
6924 "fadeIn" : 0,
6925 "fadeOut" : 0,
6926 "id" : "7E503A80-A896-4D03-9DC1-896D78C0F03A",
6927 "kind" : "audio",
6928 "linkId" : "9769E036-22A2-4F7C-95DD-F0F9F243A183",
6929 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
6930 "muted" : true,
6931 "newShot" : false,
6932 "speed" : 1,
6933 "srcIn" : 1836.390704729846,
6934 "start" : 559.6271186440678,
6935 "track" : "v1"
6936 },
6937 {
6938 "duration" : 3.6271186440678775,
6939 "fadeIn" : 0,
6940 "fadeOut" : 0,
6941 "id" : "37A7F54D-1185-4016-9C07-149400B154B8",
6942 "kind" : "audio",
6943 "linkId" : "9769E036-22A2-4F7C-95DD-F0F9F243A183",
6944 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
6945 "muted" : false,
6946 "newShot" : false,
6947 "speed" : 1,
6948 "srcIn" : 1836.437381033077,
6949 "start" : 559.6271186440678,
6950 "track" : "v2"
6951 },
6952 {
6953 "duration" : 3.6271186440678775,
6954 "fadeIn" : 0,
6955 "fadeOut" : 0,
6956 "id" : "12B7D82E-9520-4486-82BA-A1EFA3443CC5",
6957 "kind" : "video",
6958 "linkId" : "9769E036-22A2-4F7C-95DD-F0F9F243A183",
6959 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
6960 "muted" : false,
6961 "newShot" : false,
6962 "speed" : 1,
6963 "srcIn" : 1836.3007738968488,
6964 "start" : 559.6271186440678,
6965 "track" : "v3"
6966 },
6967 {
6968 "duration" : 3.6271186440678775,
6969 "fadeIn" : 0,
6970 "fadeOut" : 0,
6971 "id" : "467D66A4-ABC0-4A30-B666-EC09C21670C2",
6972 "kind" : "video",
6973 "linkId" : "9769E036-22A2-4F7C-95DD-F0F9F243A183",
6974 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
6975 "muted" : false,
6976 "newShot" : false,
6977 "speed" : 1,
6978 "srcIn" : 1836.30076931285,
6979 "start" : 559.6271186440678,
6980 "track" : "v4"
6981 },
6982 {
6983 "duration" : 5.118644067796595,
6984 "fadeIn" : 0,
6985 "fadeOut" : 0,
6986 "id" : "F7C9CC46-173D-4CEA-B84A-F83B2D94A2CE",
6987 "kind" : "video",
6988 "linkId" : "B85938EF-37B5-4F22-B0B6-0C3F5749452C",
6989 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
6990 "muted" : false,
6991 "newShot" : false,
6992 "speed" : 1,
6993 "srcIn" : 1849.1186440677973,
6994 "start" : 563.2542372881356,
6995 "track" : "v0"
6996 },
6997 {
6998 "duration" : 5.118644067796595,
6999 "fadeIn" : 0,
7000 "fadeOut" : 0,
7001 "id" : "AA8C606C-3D23-4965-B5D1-E17B4413C421",
7002 "kind" : "audio",
7003 "linkId" : "B85938EF-37B5-4F22-B0B6-0C3F5749452C",
7004 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
7005 "muted" : true,
7006 "newShot" : false,
7007 "speed" : 1,
7008 "srcIn" : 1849.0347725264562,
7009 "start" : 563.2542372881356,
7010 "track" : "v1"
7011 },
7012 {
7013 "duration" : 5.118644067796595,
7014 "fadeIn" : 0,
7015 "fadeOut" : 0,
7016 "id" : "FB4942C0-5842-419B-A4CA-242596D7CF03",
7017 "kind" : "audio",
7018 "linkId" : "B85938EF-37B5-4F22-B0B6-0C3F5749452C",
7019 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
7020 "muted" : false,
7021 "newShot" : false,
7022 "speed" : 1,
7023 "srcIn" : 1849.0814488296871,
7024 "start" : 563.2542372881356,
7025 "track" : "v2"
7026 },
7027 {
7028 "duration" : 5.118644067796595,
7029 "fadeIn" : 0,
7030 "fadeOut" : 0,
7031 "id" : "8D875994-D892-44D9-A74C-9AE0403A1240",
7032 "kind" : "video",
7033 "linkId" : "B85938EF-37B5-4F22-B0B6-0C3F5749452C",
7034 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
7035 "muted" : false,
7036 "newShot" : false,
7037 "speed" : 1,
7038 "srcIn" : 1848.944841693459,
7039 "start" : 563.2542372881356,
7040 "track" : "v3"
7041 },
7042 {
7043 "duration" : 5.118644067796595,
7044 "fadeIn" : 0,
7045 "fadeOut" : 0,
7046 "id" : "D7122A1E-0F1A-4EDA-BA83-5032DCB51740",
7047 "kind" : "video",
7048 "linkId" : "B85938EF-37B5-4F22-B0B6-0C3F5749452C",
7049 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
7050 "muted" : false,
7051 "newShot" : false,
7052 "speed" : 1,
7053 "srcIn" : 1848.9448371094602,
7054 "start" : 563.2542372881356,
7055 "track" : "v4"
7056 },
7057 {
7058 "duration" : 8.2033898305084,
7059 "fadeIn" : 0,
7060 "fadeOut" : 0,
7061 "id" : "54B76DE8-2057-470C-97CB-916B91B32B1C",
7062 "kind" : "video",
7063 "linkId" : "65CA1374-003F-413C-9F77-8A22B7EECDC3",
7064 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
7065 "muted" : false,
7066 "newShot" : false,
7067 "speed" : 1,
7068 "srcIn" : 1854.9830508474583,
7069 "start" : 568.3728813559322,
7070 "track" : "v0"
7071 },
7072 {
7073 "duration" : 8.2033898305084,
7074 "fadeIn" : 0,
7075 "fadeOut" : 0,
7076 "id" : "12851101-B365-4F9E-BFB3-FDEABAE1453A",
7077 "kind" : "audio",
7078 "linkId" : "65CA1374-003F-413C-9F77-8A22B7EECDC3",
7079 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
7080 "muted" : true,
7081 "newShot" : false,
7082 "speed" : 1,
7083 "srcIn" : 1854.8991793061173,
7084 "start" : 568.3728813559322,
7085 "track" : "v1"
7086 },
7087 {
7088 "duration" : 8.2033898305084,
7089 "fadeIn" : 0,
7090 "fadeOut" : 0,
7091 "id" : "38DCBA3E-C18E-447C-B319-9E3A3DE4DC21",
7092 "kind" : "audio",
7093 "linkId" : "65CA1374-003F-413C-9F77-8A22B7EECDC3",
7094 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
7095 "muted" : false,
7096 "newShot" : false,
7097 "speed" : 1,
7098 "srcIn" : 1854.9458556093482,
7099 "start" : 568.3728813559322,
7100 "track" : "v2"
7101 },
7102 {
7103 "duration" : 8.2033898305084,
7104 "fadeIn" : 0,
7105 "fadeOut" : 0,
7106 "id" : "FE05A76D-4FB5-43E6-9960-DE92DC23FD6C",
7107 "kind" : "video",
7108 "linkId" : "65CA1374-003F-413C-9F77-8A22B7EECDC3",
7109 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
7110 "muted" : false,
7111 "newShot" : false,
7112 "speed" : 1,
7113 "srcIn" : 1854.80924847312,
7114 "start" : 568.3728813559322,
7115 "track" : "v3"
7116 },
7117 {
7118 "duration" : 8.2033898305084,
7119 "fadeIn" : 0,
7120 "fadeOut" : 0,
7121 "id" : "35C17BC8-29A1-44B4-A655-A1AF25C6D713",
7122 "kind" : "video",
7123 "linkId" : "65CA1374-003F-413C-9F77-8A22B7EECDC3",
7124 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
7125 "muted" : false,
7126 "newShot" : false,
7127 "speed" : 1,
7128 "srcIn" : 1854.8092438891213,
7129 "start" : 568.3728813559322,
7130 "track" : "v4"
7131 },
7132 {
7133 "duration" : 5.559322033898297,
7134 "fadeIn" : 0,
7135 "fadeOut" : 0,
7136 "id" : "ED705251-681D-4EEE-81C8-D077FBB072B5",
7137 "kind" : "video",
7138 "linkId" : "FD6E0C12-08F2-474C-B889-10F178952804",
7139 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
7140 "muted" : false,
7141 "newShot" : false,
7142 "speed" : 1,
7143 "srcIn" : 1865.525423728814,
7144 "start" : 576.5762711864406,
7145 "track" : "v0"
7146 },
7147 {
7148 "duration" : 5.559322033898297,
7149 "fadeIn" : 0,
7150 "fadeOut" : 0,
7151 "id" : "A57F8B52-605A-4BB2-8B24-0CBC8B78EBAB",
7152 "kind" : "audio",
7153 "linkId" : "FD6E0C12-08F2-474C-B889-10F178952804",
7154 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
7155 "muted" : true,
7156 "newShot" : false,
7157 "speed" : 1,
7158 "srcIn" : 1865.441552187473,
7159 "start" : 576.5762711864406,
7160 "track" : "v1"
7161 },
7162 {
7163 "duration" : 5.559322033898297,
7164 "fadeIn" : 0,
7165 "fadeOut" : 0,
7166 "id" : "93D09BC6-1947-40D5-82BA-E6E1035452E2",
7167 "kind" : "audio",
7168 "linkId" : "FD6E0C12-08F2-474C-B889-10F178952804",
7169 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
7170 "muted" : false,
7171 "newShot" : false,
7172 "speed" : 1,
7173 "srcIn" : 1865.488228490704,
7174 "start" : 576.5762711864406,
7175 "track" : "v2"
7176 },
7177 {
7178 "duration" : 5.559322033898297,
7179 "fadeIn" : 0,
7180 "fadeOut" : 0,
7181 "id" : "48D771B8-EC5B-45CD-842D-CFC4F85FC9D3",
7182 "kind" : "video",
7183 "linkId" : "FD6E0C12-08F2-474C-B889-10F178952804",
7184 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
7185 "muted" : false,
7186 "newShot" : false,
7187 "speed" : 1,
7188 "srcIn" : 1865.3516213544758,
7189 "start" : 576.5762711864406,
7190 "track" : "v3"
7191 },
7192 {
7193 "duration" : 5.559322033898297,
7194 "fadeIn" : 0,
7195 "fadeOut" : 0,
7196 "id" : "6F0B9EDA-F628-4403-9AC9-D23443721AA7",
7197 "kind" : "video",
7198 "linkId" : "FD6E0C12-08F2-474C-B889-10F178952804",
7199 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
7200 "muted" : false,
7201 "newShot" : false,
7202 "speed" : 1,
7203 "srcIn" : 1865.351616770477,
7204 "start" : 576.5762711864406,
7205 "track" : "v4"
7206 },
7207 {
7208 "duration" : 18.881355932203405,
7209 "fadeIn" : 0,
7210 "fadeOut" : 0,
7211 "id" : "B0DF95C7-AB53-4E4B-B365-DA47C88E233F",
7212 "kind" : "video",
7213 "linkId" : "17C6BBA2-9064-425D-8592-73511CF2CF98",
7214 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
7215 "muted" : false,
7216 "newShot" : false,
7217 "speed" : 1,
7218 "srcIn" : 1938.101694915255,
7219 "start" : 582.1355932203389,
7220 "track" : "v0"
7221 },
7222 {
7223 "duration" : 18.881355932203405,
7224 "fadeIn" : 0,
7225 "fadeOut" : 0,
7226 "id" : "10C2DDB7-13BF-4707-A836-146E1C1DF546",
7227 "kind" : "audio",
7228 "linkId" : "17C6BBA2-9064-425D-8592-73511CF2CF98",
7229 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
7230 "muted" : true,
7231 "newShot" : false,
7232 "speed" : 1,
7233 "srcIn" : 1938.0178233739139,
7234 "start" : 582.1355932203389,
7235 "track" : "v1"
7236 },
7237 {
7238 "duration" : 18.881355932203405,
7239 "fadeIn" : 0,
7240 "fadeOut" : 0,
7241 "id" : "D426D540-67E5-4B2C-8355-17C9D9356D6E",
7242 "kind" : "audio",
7243 "linkId" : "17C6BBA2-9064-425D-8592-73511CF2CF98",
7244 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
7245 "muted" : false,
7246 "newShot" : false,
7247 "speed" : 1,
7248 "srcIn" : 1938.0644996771448,
7249 "start" : 582.1355932203389,
7250 "track" : "v2"
7251 },
7252 {
7253 "duration" : 18.881355932203405,
7254 "fadeIn" : 0,
7255 "fadeOut" : 0,
7256 "id" : "4270D3EC-C6D9-44CD-BAEF-E087D96DEE47",
7257 "kind" : "video",
7258 "linkId" : "17C6BBA2-9064-425D-8592-73511CF2CF98",
7259 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
7260 "muted" : false,
7261 "newShot" : false,
7262 "speed" : 1,
7263 "srcIn" : 1937.9278925409167,
7264 "start" : 582.1355932203389,
7265 "track" : "v3"
7266 },
7267 {
7268 "duration" : 18.881355932203405,
7269 "fadeIn" : 0,
7270 "fadeOut" : 0,
7271 "id" : "4D883F8E-FE22-4FD7-80D8-05BB6DF23E69",
7272 "kind" : "video",
7273 "linkId" : "17C6BBA2-9064-425D-8592-73511CF2CF98",
7274 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
7275 "muted" : false,
7276 "newShot" : false,
7277 "speed" : 1,
7278 "srcIn" : 1937.9278879569179,
7279 "start" : 582.1355932203389,
7280 "track" : "v4"
7281 },
7282 {
7283 "duration" : 6.237288135593303,
7284 "fadeIn" : 0,
7285 "fadeOut" : 0,
7286 "id" : "73E5F742-AA2F-4AD8-89B1-D1CD80D854E7",
7287 "kind" : "video",
7288 "linkId" : "338C9A6C-870B-46F8-8240-2067335F8F8A",
7289 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
7290 "muted" : false,
7291 "newShot" : false,
7292 "speed" : 1,
7293 "srcIn" : 1956.9830508474583,
7294 "start" : 601.0169491525423,
7295 "track" : "v0"
7296 },
7297 {
7298 "duration" : 6.237288135593303,
7299 "fadeIn" : 0,
7300 "fadeOut" : 0,
7301 "id" : "01D1EA2C-B8BB-4B95-8C2F-716EE57867DE",
7302 "kind" : "audio",
7303 "linkId" : "338C9A6C-870B-46F8-8240-2067335F8F8A",
7304 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
7305 "muted" : true,
7306 "newShot" : false,
7307 "speed" : 1,
7308 "srcIn" : 1956.8991793061173,
7309 "start" : 601.0169491525423,
7310 "track" : "v1"
7311 },
7312 {
7313 "duration" : 6.237288135593303,
7314 "fadeIn" : 0,
7315 "fadeOut" : 0,
7316 "id" : "588AAA62-D950-4167-88E1-3A7E9016735E",
7317 "kind" : "audio",
7318 "linkId" : "338C9A6C-870B-46F8-8240-2067335F8F8A",
7319 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
7320 "muted" : false,
7321 "newShot" : false,
7322 "speed" : 1,
7323 "srcIn" : 1956.9458556093482,
7324 "start" : 601.0169491525423,
7325 "track" : "v2"
7326 },
7327 {
7328 "duration" : 6.237288135593303,
7329 "fadeIn" : 0,
7330 "fadeOut" : 0,
7331 "id" : "5F41A84E-E3E3-417E-B7E8-82995513D4E2",
7332 "kind" : "video",
7333 "linkId" : "338C9A6C-870B-46F8-8240-2067335F8F8A",
7334 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
7335 "muted" : false,
7336 "newShot" : false,
7337 "speed" : 1,
7338 "srcIn" : 1956.80924847312,
7339 "start" : 601.0169491525423,
7340 "track" : "v3"
7341 },
7342 {
7343 "duration" : 6.237288135593303,
7344 "fadeIn" : 0,
7345 "fadeOut" : 0,
7346 "id" : "06DE79D5-6984-41FF-8120-821047F987C5",
7347 "kind" : "video",
7348 "linkId" : "338C9A6C-870B-46F8-8240-2067335F8F8A",
7349 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
7350 "muted" : false,
7351 "newShot" : false,
7352 "speed" : 1,
7353 "srcIn" : 1956.8092438891213,
7354 "start" : 601.0169491525423,
7355 "track" : "v4"
7356 },
7357 {
7358 "duration" : 10.677966101694892,
7359 "fadeIn" : 0,
7360 "fadeOut" : 0,
7361 "id" : "F7574C8D-473A-44C7-BB33-CA9C31856531",
7362 "kind" : "video",
7363 "linkId" : "5715D6B2-69B7-4806-A021-82A001C82B8D",
7364 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
7365 "muted" : false,
7366 "newShot" : false,
7367 "speed" : 1,
7368 "srcIn" : 2022.8474576271192,
7369 "start" : 607.2542372881356,
7370 "track" : "v0"
7371 },
7372 {
7373 "duration" : 10.677966101694892,
7374 "fadeIn" : 0,
7375 "fadeOut" : 0,
7376 "id" : "6F7C3CE2-9238-415C-A314-90B677ECAE17",
7377 "kind" : "audio",
7378 "linkId" : "5715D6B2-69B7-4806-A021-82A001C82B8D",
7379 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
7380 "muted" : true,
7381 "newShot" : false,
7382 "speed" : 1,
7383 "srcIn" : 2022.7635860857781,
7384 "start" : 607.2542372881356,
7385 "track" : "v1"
7386 },
7387 {
7388 "duration" : 10.677966101694892,
7389 "fadeIn" : 0,
7390 "fadeOut" : 0,
7391 "id" : "BC1D7F8D-872D-4749-97D4-9CBF947EB008",
7392 "kind" : "audio",
7393 "linkId" : "5715D6B2-69B7-4806-A021-82A001C82B8D",
7394 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
7395 "muted" : false,
7396 "newShot" : false,
7397 "speed" : 1,
7398 "srcIn" : 2022.810262389009,
7399 "start" : 607.2542372881356,
7400 "track" : "v2"
7401 },
7402 {
7403 "duration" : 10.677966101694892,
7404 "fadeIn" : 0,
7405 "fadeOut" : 0,
7406 "id" : "5AC16FB7-ABB0-462A-926D-6657645660DB",
7407 "kind" : "video",
7408 "linkId" : "5715D6B2-69B7-4806-A021-82A001C82B8D",
7409 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
7410 "muted" : false,
7411 "newShot" : false,
7412 "speed" : 1,
7413 "srcIn" : 2022.673655252781,
7414 "start" : 607.2542372881356,
7415 "track" : "v3"
7416 },
7417 {
7418 "duration" : 10.677966101694892,
7419 "fadeIn" : 0,
7420 "fadeOut" : 0,
7421 "id" : "7EC1D808-B244-481D-AEED-29FBAD17A539",
7422 "kind" : "video",
7423 "linkId" : "5715D6B2-69B7-4806-A021-82A001C82B8D",
7424 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
7425 "muted" : false,
7426 "newShot" : false,
7427 "speed" : 1,
7428 "srcIn" : 2022.673650668782,
7429 "start" : 607.2542372881356,
7430 "track" : "v4"
7431 },
7432 {
7433 "duration" : 3.627118644067764,
7434 "fadeIn" : 0,
7435 "fadeOut" : 0,
7436 "id" : "60AAA140-4B79-4568-92FB-E8B2070677B4",
7437 "kind" : "video",
7438 "linkId" : "7A62ADBD-0427-472A-880B-89AAE3A2A8F4",
7439 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
7440 "muted" : false,
7441 "newShot" : false,
7442 "speed" : 1,
7443 "srcIn" : 2045.2881355932209,
7444 "start" : 617.9322033898305,
7445 "track" : "v0"
7446 },
7447 {
7448 "duration" : 3.627118644067764,
7449 "fadeIn" : 0,
7450 "fadeOut" : 0,
7451 "id" : "AA1ADCAE-DA36-4FF1-A7CC-6C3B75A693B7",
7452 "kind" : "audio",
7453 "linkId" : "7A62ADBD-0427-472A-880B-89AAE3A2A8F4",
7454 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
7455 "muted" : true,
7456 "newShot" : false,
7457 "speed" : 1,
7458 "srcIn" : 2045.2042640518798,
7459 "start" : 617.9322033898305,
7460 "track" : "v1"
7461 },
7462 {
7463 "duration" : 3.627118644067764,
7464 "fadeIn" : 0,
7465 "fadeOut" : 0,
7466 "id" : "C3588EA8-F756-402E-B6EC-A9C9772EB504",
7467 "kind" : "audio",
7468 "linkId" : "7A62ADBD-0427-472A-880B-89AAE3A2A8F4",
7469 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
7470 "muted" : false,
7471 "newShot" : false,
7472 "speed" : 1,
7473 "srcIn" : 2045.2509403551107,
7474 "start" : 617.9322033898305,
7475 "track" : "v2"
7476 },
7477 {
7478 "duration" : 3.627118644067764,
7479 "fadeIn" : 0,
7480 "fadeOut" : 0,
7481 "id" : "8A86F818-17D6-4DA6-806A-BD6E61EDD16A",
7482 "kind" : "video",
7483 "linkId" : "7A62ADBD-0427-472A-880B-89AAE3A2A8F4",
7484 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
7485 "muted" : false,
7486 "newShot" : false,
7487 "speed" : 1,
7488 "srcIn" : 2045.1143332188826,
7489 "start" : 617.9322033898305,
7490 "track" : "v3"
7491 },
7492 {
7493 "duration" : 3.627118644067764,
7494 "fadeIn" : 0,
7495 "fadeOut" : 0,
7496 "id" : "DF8175BF-3E2D-4B78-8761-2798CBEEB331",
7497 "kind" : "video",
7498 "linkId" : "7A62ADBD-0427-472A-880B-89AAE3A2A8F4",
7499 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
7500 "muted" : false,
7501 "newShot" : false,
7502 "speed" : 1,
7503 "srcIn" : 2045.1143286348838,
7504 "start" : 617.9322033898305,
7505 "track" : "v4"
7506 },
7507 {
7508 "duration" : 2.4067796610169125,
7509 "fadeIn" : 0,
7510 "fadeOut" : 0,
7511 "id" : "0079348D-917B-4B45-A779-BE7BDA967808",
7512 "kind" : "video",
7513 "linkId" : "B5A3F4FE-CCAA-45AE-A6FB-0579F3BF1536",
7514 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
7515 "muted" : false,
7516 "newShot" : false,
7517 "speed" : 1,
7518 "srcIn" : 2050.542372881356,
7519 "start" : 621.5593220338983,
7520 "track" : "v0"
7521 },
7522 {
7523 "duration" : 2.4067796610169125,
7524 "fadeIn" : 0,
7525 "fadeOut" : 0,
7526 "id" : "2D8FE96C-46AE-4C66-B4A5-30D8857C61FB",
7527 "kind" : "audio",
7528 "linkId" : "B5A3F4FE-CCAA-45AE-A6FB-0579F3BF1536",
7529 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
7530 "muted" : true,
7531 "newShot" : false,
7532 "speed" : 1,
7533 "srcIn" : 2050.458501340015,
7534 "start" : 621.5593220338983,
7535 "track" : "v1"
7536 },
7537 {
7538 "duration" : 2.4067796610169125,
7539 "fadeIn" : 0,
7540 "fadeOut" : 0,
7541 "id" : "B675F446-CEBF-4F4A-B344-F62184FADF3B",
7542 "kind" : "audio",
7543 "linkId" : "B5A3F4FE-CCAA-45AE-A6FB-0579F3BF1536",
7544 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
7545 "muted" : false,
7546 "newShot" : false,
7547 "speed" : 1,
7548 "srcIn" : 2050.505177643246,
7549 "start" : 621.5593220338983,
7550 "track" : "v2"
7551 },
7552 {
7553 "duration" : 2.4067796610169125,
7554 "fadeIn" : 0,
7555 "fadeOut" : 0,
7556 "id" : "C21A6286-77CE-4A32-B617-2710BCE8CBD9",
7557 "kind" : "video",
7558 "linkId" : "B5A3F4FE-CCAA-45AE-A6FB-0579F3BF1536",
7559 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
7560 "muted" : false,
7561 "newShot" : false,
7562 "speed" : 1,
7563 "srcIn" : 2050.368570507018,
7564 "start" : 621.5593220338983,
7565 "track" : "v3"
7566 },
7567 {
7568 "duration" : 2.4067796610169125,
7569 "fadeIn" : 0,
7570 "fadeOut" : 0,
7571 "id" : "64279978-B9D7-4E8D-AFED-D7913E1B9532",
7572 "kind" : "video",
7573 "linkId" : "B5A3F4FE-CCAA-45AE-A6FB-0579F3BF1536",
7574 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
7575 "muted" : false,
7576 "newShot" : false,
7577 "speed" : 1,
7578 "srcIn" : 2050.368565923019,
7579 "start" : 621.5593220338983,
7580 "track" : "v4"
7581 },
7582 {
7583 "duration" : 3.4237288135593644,
7584 "fadeIn" : 0,
7585 "fadeOut" : 0,
7586 "id" : "A040FD6A-EC96-415F-8D28-7B4691F99BF3",
7587 "kind" : "video",
7588 "linkId" : "0F6F0554-6C53-4419-B9B3-D9C2EE7D2267",
7589 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
7590 "muted" : false,
7591 "newShot" : false,
7592 "speed" : 1,
7593 "srcIn" : 2052.949152542373,
7594 "start" : 623.9661016949152,
7595 "track" : "v0"
7596 },
7597 {
7598 "duration" : 3.4237288135593644,
7599 "fadeIn" : 0,
7600 "fadeOut" : 0,
7601 "id" : "6AC76567-F75D-4A33-B204-A10F431BBFDD",
7602 "kind" : "audio",
7603 "linkId" : "0F6F0554-6C53-4419-B9B3-D9C2EE7D2267",
7604 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
7605 "muted" : true,
7606 "newShot" : false,
7607 "speed" : 1,
7608 "srcIn" : 2052.865281001032,
7609 "start" : 623.9661016949152,
7610 "track" : "v1"
7611 },
7612 {
7613 "duration" : 3.4237288135593644,
7614 "fadeIn" : 0,
7615 "fadeOut" : 0,
7616 "id" : "FA9C2797-0EB0-4B75-A617-5D4CF590AC43",
7617 "kind" : "audio",
7618 "linkId" : "0F6F0554-6C53-4419-B9B3-D9C2EE7D2267",
7619 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
7620 "muted" : false,
7621 "newShot" : false,
7622 "speed" : 1,
7623 "srcIn" : 2052.911957304263,
7624 "start" : 623.9661016949152,
7625 "track" : "v2"
7626 },
7627 {
7628 "duration" : 3.4237288135593644,
7629 "fadeIn" : 0,
7630 "fadeOut" : 0,
7631 "id" : "429309C1-5820-4F55-97F5-8FBD04302BA5",
7632 "kind" : "video",
7633 "linkId" : "0F6F0554-6C53-4419-B9B3-D9C2EE7D2267",
7634 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
7635 "muted" : false,
7636 "newShot" : false,
7637 "speed" : 1,
7638 "srcIn" : 2052.775350168035,
7639 "start" : 623.9661016949152,
7640 "track" : "v3"
7641 },
7642 {
7643 "duration" : 3.4237288135593644,
7644 "fadeIn" : 0,
7645 "fadeOut" : 0,
7646 "id" : "89C2D014-50A7-43ED-AE56-0AA708FA28ED",
7647 "kind" : "video",
7648 "linkId" : "0F6F0554-6C53-4419-B9B3-D9C2EE7D2267",
7649 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
7650 "muted" : false,
7651 "newShot" : false,
7652 "speed" : 1,
7653 "srcIn" : 2052.775345584036,
7654 "start" : 623.9661016949152,
7655 "track" : "v4"
7656 },
7657 {
7658 "duration" : 5.69491525423723,
7659 "fadeIn" : 0,
7660 "fadeOut" : 0,
7661 "id" : "01E331DE-F3BA-42B2-B95C-4FA7802600C4",
7662 "kind" : "video",
7663 "linkId" : "681599CE-614B-4D54-9956-0A2F59A0C020",
7664 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
7665 "muted" : false,
7666 "newShot" : false,
7667 "speed" : 1,
7668 "srcIn" : 2114.6779661016953,
7669 "start" : 627.3898305084746,
7670 "track" : "v0"
7671 },
7672 {
7673 "duration" : 5.69491525423723,
7674 "fadeIn" : 0,
7675 "fadeOut" : 0,
7676 "id" : "602A3DDB-AB74-45B5-8FAE-A660E6CA2700",
7677 "kind" : "audio",
7678 "linkId" : "681599CE-614B-4D54-9956-0A2F59A0C020",
7679 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
7680 "muted" : true,
7681 "newShot" : false,
7682 "speed" : 1,
7683 "srcIn" : 2114.5940945603543,
7684 "start" : 627.3898305084746,
7685 "track" : "v1"
7686 },
7687 {
7688 "duration" : 5.69491525423723,
7689 "fadeIn" : 0,
7690 "fadeOut" : 0,
7691 "id" : "0BA636D9-34A5-47E7-9BC5-0BDD5579680E",
7692 "kind" : "audio",
7693 "linkId" : "681599CE-614B-4D54-9956-0A2F59A0C020",
7694 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
7695 "muted" : false,
7696 "newShot" : false,
7697 "speed" : 1,
7698 "srcIn" : 2114.640770863585,
7699 "start" : 627.3898305084746,
7700 "track" : "v2"
7701 },
7702 {
7703 "duration" : 5.69491525423723,
7704 "fadeIn" : 0,
7705 "fadeOut" : 0,
7706 "id" : "4E01905E-2230-4C63-9855-875F6C89C92B",
7707 "kind" : "video",
7708 "linkId" : "681599CE-614B-4D54-9956-0A2F59A0C020",
7709 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
7710 "muted" : false,
7711 "newShot" : false,
7712 "speed" : 1,
7713 "srcIn" : 2114.504163727357,
7714 "start" : 627.3898305084746,
7715 "track" : "v3"
7716 },
7717 {
7718 "duration" : 5.69491525423723,
7719 "fadeIn" : 0,
7720 "fadeOut" : 0,
7721 "id" : "78183D76-36AF-4F77-8481-6336BB1F1340",
7722 "kind" : "video",
7723 "linkId" : "681599CE-614B-4D54-9956-0A2F59A0C020",
7724 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
7725 "muted" : false,
7726 "newShot" : false,
7727 "speed" : 1,
7728 "srcIn" : 2114.5041591433583,
7729 "start" : 627.3898305084746,
7730 "track" : "v4"
7731 },
7732 {
7733 "duration" : 15.76271186440681,
7734 "fadeIn" : 0,
7735 "fadeOut" : 0,
7736 "id" : "581AB06A-CA87-43B3-9083-3BF4CA1B488F",
7737 "kind" : "video",
7738 "linkId" : "54C20EBF-C3C2-46DA-929B-9C51CBA0B7D6",
7739 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
7740 "muted" : false,
7741 "newShot" : false,
7742 "speed" : 1,
7743 "srcIn" : 2230.1694915254243,
7744 "start" : 633.0847457627118,
7745 "track" : "v0"
7746 },
7747 {
7748 "duration" : 15.76271186440681,
7749 "fadeIn" : 0,
7750 "fadeOut" : 0,
7751 "id" : "39BA14C0-0C38-4545-B0B0-C33EB72363EF",
7752 "kind" : "audio",
7753 "linkId" : "54C20EBF-C3C2-46DA-929B-9C51CBA0B7D6",
7754 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
7755 "muted" : true,
7756 "newShot" : false,
7757 "speed" : 1,
7758 "srcIn" : 2230.085619984083,
7759 "start" : 633.0847457627118,
7760 "track" : "v1"
7761 },
7762 {
7763 "duration" : 15.76271186440681,
7764 "fadeIn" : 0,
7765 "fadeOut" : 0,
7766 "id" : "2161011F-CC8E-4C61-8661-7A947C751069",
7767 "kind" : "audio",
7768 "linkId" : "54C20EBF-C3C2-46DA-929B-9C51CBA0B7D6",
7769 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
7770 "muted" : false,
7771 "newShot" : false,
7772 "speed" : 1,
7773 "srcIn" : 2230.132296287314,
7774 "start" : 633.0847457627118,
7775 "track" : "v2"
7776 },
7777 {
7778 "duration" : 15.76271186440681,
7779 "fadeIn" : 0,
7780 "fadeOut" : 0,
7781 "id" : "7265B315-57C1-47FB-A27C-3AB7B8CA1004",
7782 "kind" : "video",
7783 "linkId" : "54C20EBF-C3C2-46DA-929B-9C51CBA0B7D6",
7784 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
7785 "muted" : false,
7786 "newShot" : false,
7787 "speed" : 1,
7788 "srcIn" : 2229.995689151086,
7789 "start" : 633.0847457627118,
7790 "track" : "v3"
7791 },
7792 {
7793 "duration" : 15.76271186440681,
7794 "fadeIn" : 0,
7795 "fadeOut" : 0,
7796 "id" : "EFC80E51-0F05-4B16-8596-E78F73CB045F",
7797 "kind" : "video",
7798 "linkId" : "54C20EBF-C3C2-46DA-929B-9C51CBA0B7D6",
7799 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
7800 "muted" : false,
7801 "newShot" : false,
7802 "speed" : 1,
7803 "srcIn" : 2229.995684567087,
7804 "start" : 633.0847457627118,
7805 "track" : "v4"
7806 },
7807 {
7808 "duration" : 3.3898305084745743,
7809 "fadeIn" : 0,
7810 "fadeOut" : 0,
7811 "id" : "8BD87ADB-1AA9-4B14-9370-4DF20E448896",
7812 "kind" : "video",
7813 "linkId" : "C2740056-FAEB-4E91-9E00-112253797C46",
7814 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
7815 "muted" : false,
7816 "newShot" : false,
7817 "speed" : 1,
7818 "srcIn" : 2256.372881355933,
7819 "start" : 648.8474576271186,
7820 "track" : "v0"
7821 },
7822 {
7823 "duration" : 3.3898305084745743,
7824 "fadeIn" : 0,
7825 "fadeOut" : 0,
7826 "id" : "7A48AFF3-E08F-437C-AC57-92133246569E",
7827 "kind" : "audio",
7828 "linkId" : "C2740056-FAEB-4E91-9E00-112253797C46",
7829 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
7830 "muted" : true,
7831 "newShot" : false,
7832 "speed" : 1,
7833 "srcIn" : 2256.2890098145917,
7834 "start" : 648.8474576271186,
7835 "track" : "v1"
7836 },
7837 {
7838 "duration" : 3.3898305084745743,
7839 "fadeIn" : 0,
7840 "fadeOut" : 0,
7841 "id" : "E427E26F-1FDB-4742-A1E4-015E32255D38",
7842 "kind" : "audio",
7843 "linkId" : "C2740056-FAEB-4E91-9E00-112253797C46",
7844 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
7845 "muted" : false,
7846 "newShot" : false,
7847 "speed" : 1,
7848 "srcIn" : 2256.3356861178227,
7849 "start" : 648.8474576271186,
7850 "track" : "v2"
7851 },
7852 {
7853 "duration" : 3.3898305084745743,
7854 "fadeIn" : 0,
7855 "fadeOut" : 0,
7856 "id" : "107EE1C9-66CC-4E8E-9743-C6900E649621",
7857 "kind" : "video",
7858 "linkId" : "C2740056-FAEB-4E91-9E00-112253797C46",
7859 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
7860 "muted" : false,
7861 "newShot" : false,
7862 "speed" : 1,
7863 "srcIn" : 2256.1990789815945,
7864 "start" : 648.8474576271186,
7865 "track" : "v3"
7866 },
7867 {
7868 "duration" : 3.3898305084745743,
7869 "fadeIn" : 0,
7870 "fadeOut" : 0,
7871 "id" : "D7AAC444-EA58-4486-9341-92E3C325D17F",
7872 "kind" : "video",
7873 "linkId" : "C2740056-FAEB-4E91-9E00-112253797C46",
7874 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
7875 "muted" : false,
7876 "newShot" : false,
7877 "speed" : 1,
7878 "srcIn" : 2256.1990743975957,
7879 "start" : 648.8474576271186,
7880 "track" : "v4"
7881 },
7882 {
7883 "duration" : 15.423728813559364,
7884 "fadeIn" : 0,
7885 "fadeOut" : 0,
7886 "id" : "4754E275-63E9-4C43-8DBD-3771650BDEB8",
7887 "kind" : "video",
7888 "linkId" : "FF0D729E-3013-4F28-A488-DBD70296587F",
7889 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
7890 "muted" : false,
7891 "newShot" : false,
7892 "speed" : 1,
7893 "srcIn" : 2280.9491525423737,
7894 "start" : 652.2372881355932,
7895 "track" : "v0"
7896 },
7897 {
7898 "duration" : 15.423728813559364,
7899 "fadeIn" : 0,
7900 "fadeOut" : 0,
7901 "id" : "A8BCA6E8-0804-4D39-8229-30DF2DFBF320",
7902 "kind" : "audio",
7903 "linkId" : "FF0D729E-3013-4F28-A488-DBD70296587F",
7904 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
7905 "muted" : true,
7906 "newShot" : false,
7907 "speed" : 1,
7908 "srcIn" : 2280.8652810010326,
7909 "start" : 652.2372881355932,
7910 "track" : "v1"
7911 },
7912 {
7913 "duration" : 15.423728813559364,
7914 "fadeIn" : 0,
7915 "fadeOut" : 0,
7916 "id" : "2A8D5833-B3BC-4A82-A7E7-6319DFF51DC3",
7917 "kind" : "audio",
7918 "linkId" : "FF0D729E-3013-4F28-A488-DBD70296587F",
7919 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
7920 "muted" : false,
7921 "newShot" : false,
7922 "speed" : 1,
7923 "srcIn" : 2280.9119573042635,
7924 "start" : 652.2372881355932,
7925 "track" : "v2"
7926 },
7927 {
7928 "duration" : 15.423728813559364,
7929 "fadeIn" : 0,
7930 "fadeOut" : 0,
7931 "id" : "B8963AE1-23D3-4C35-B241-00A5396F4DD7",
7932 "kind" : "video",
7933 "linkId" : "FF0D729E-3013-4F28-A488-DBD70296587F",
7934 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
7935 "muted" : false,
7936 "newShot" : false,
7937 "speed" : 1,
7938 "srcIn" : 2280.7753501680354,
7939 "start" : 652.2372881355932,
7940 "track" : "v3"
7941 },
7942 {
7943 "duration" : 15.423728813559364,
7944 "fadeIn" : 0,
7945 "fadeOut" : 0,
7946 "id" : "1FC80A57-7A45-4C7D-9200-0C1A48A3D6F5",
7947 "kind" : "video",
7948 "linkId" : "FF0D729E-3013-4F28-A488-DBD70296587F",
7949 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
7950 "muted" : false,
7951 "newShot" : false,
7952 "speed" : 1,
7953 "srcIn" : 2280.7753455840366,
7954 "start" : 652.2372881355932,
7955 "track" : "v4"
7956 },
7957 {
7958 "duration" : 12.745762711864359,
7959 "fadeIn" : 0,
7960 "fadeOut" : 0,
7961 "id" : "0CA818E8-62B3-474A-8050-49368421A90E",
7962 "kind" : "video",
7963 "linkId" : "C5B9230A-8789-4FD4-B107-6702F012D99A",
7964 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
7965 "muted" : false,
7966 "newShot" : false,
7967 "speed" : 1,
7968 "srcIn" : 2296.372881355933,
7969 "start" : 667.6610169491526,
7970 "track" : "v0"
7971 },
7972 {
7973 "duration" : 12.745762711864359,
7974 "fadeIn" : 0,
7975 "fadeOut" : 0,
7976 "id" : "EB3722EC-6C38-40B2-B519-694180267E3A",
7977 "kind" : "audio",
7978 "linkId" : "C5B9230A-8789-4FD4-B107-6702F012D99A",
7979 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
7980 "muted" : true,
7981 "newShot" : false,
7982 "speed" : 1,
7983 "srcIn" : 2296.2890098145917,
7984 "start" : 667.6610169491526,
7985 "track" : "v1"
7986 },
7987 {
7988 "duration" : 12.745762711864359,
7989 "fadeIn" : 0,
7990 "fadeOut" : 0,
7991 "id" : "A797E488-873A-4FC3-816A-D61A73E2F1D9",
7992 "kind" : "audio",
7993 "linkId" : "C5B9230A-8789-4FD4-B107-6702F012D99A",
7994 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
7995 "muted" : false,
7996 "newShot" : false,
7997 "speed" : 1,
7998 "srcIn" : 2296.3356861178227,
7999 "start" : 667.6610169491526,
8000 "track" : "v2"
8001 },
8002 {
8003 "duration" : 12.745762711864359,
8004 "fadeIn" : 0,
8005 "fadeOut" : 0,
8006 "id" : "75FC92A1-B149-4AB0-A366-35F476711F59",
8007 "kind" : "video",
8008 "linkId" : "C5B9230A-8789-4FD4-B107-6702F012D99A",
8009 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
8010 "muted" : false,
8011 "newShot" : false,
8012 "speed" : 1,
8013 "srcIn" : 2296.1990789815945,
8014 "start" : 667.6610169491526,
8015 "track" : "v3"
8016 },
8017 {
8018 "duration" : 12.745762711864359,
8019 "fadeIn" : 0,
8020 "fadeOut" : 0,
8021 "id" : "5B3388A0-5AC5-481F-8DD8-BED12840C1D9",
8022 "kind" : "video",
8023 "linkId" : "C5B9230A-8789-4FD4-B107-6702F012D99A",
8024 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
8025 "muted" : false,
8026 "newShot" : false,
8027 "speed" : 1,
8028 "srcIn" : 2296.1990743975957,
8029 "start" : 667.6610169491526,
8030 "track" : "v4"
8031 },
8032 {
8033 "duration" : 4.677966101695006,
8034 "fadeIn" : 0,
8035 "fadeOut" : 0,
8036 "id" : "5A8649A2-00AB-447F-BC8C-3085E8695184",
8037 "kind" : "video",
8038 "linkId" : "2782DABC-F157-44FA-B15C-F2D70A28D22C",
8039 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
8040 "muted" : false,
8041 "newShot" : false,
8042 "speed" : 1,
8043 "srcIn" : 2313.254237288136,
8044 "start" : 680.4067796610169,
8045 "track" : "v0"
8046 },
8047 {
8048 "duration" : 4.677966101695006,
8049 "fadeIn" : 0,
8050 "fadeOut" : 0,
8051 "id" : "EAC07860-D644-40FF-9A4D-08BD56F332C4",
8052 "kind" : "audio",
8053 "linkId" : "2782DABC-F157-44FA-B15C-F2D70A28D22C",
8054 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
8055 "muted" : true,
8056 "newShot" : false,
8057 "speed" : 1,
8058 "srcIn" : 2313.170365746795,
8059 "start" : 680.4067796610169,
8060 "track" : "v1"
8061 },
8062 {
8063 "duration" : 4.677966101695006,
8064 "fadeIn" : 0,
8065 "fadeOut" : 0,
8066 "id" : "0950EA6E-C41F-453A-9C62-F24BC14CD76E",
8067 "kind" : "audio",
8068 "linkId" : "2782DABC-F157-44FA-B15C-F2D70A28D22C",
8069 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
8070 "muted" : false,
8071 "newShot" : false,
8072 "speed" : 1,
8073 "srcIn" : 2313.217042050026,
8074 "start" : 680.4067796610169,
8075 "track" : "v2"
8076 },
8077 {
8078 "duration" : 4.677966101695006,
8079 "fadeIn" : 0,
8080 "fadeOut" : 0,
8081 "id" : "C0EBCCE6-06A8-4958-8643-35B908EA7C98",
8082 "kind" : "video",
8083 "linkId" : "2782DABC-F157-44FA-B15C-F2D70A28D22C",
8084 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
8085 "muted" : false,
8086 "newShot" : false,
8087 "speed" : 1,
8088 "srcIn" : 2313.080434913798,
8089 "start" : 680.4067796610169,
8090 "track" : "v3"
8091 },
8092 {
8093 "duration" : 4.677966101695006,
8094 "fadeIn" : 0,
8095 "fadeOut" : 0,
8096 "id" : "7D753BA0-2851-4A2B-B1AB-52234E029F7C",
8097 "kind" : "video",
8098 "linkId" : "2782DABC-F157-44FA-B15C-F2D70A28D22C",
8099 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
8100 "muted" : false,
8101 "newShot" : false,
8102 "speed" : 1,
8103 "srcIn" : 2313.080430329799,
8104 "start" : 680.4067796610169,
8105 "track" : "v4"
8106 },
8107 {
8108 "duration" : 19.186440677966175,
8109 "fadeIn" : 0,
8110 "fadeOut" : 0,
8111 "id" : "B4AB14C4-4491-499A-8505-0934B993F07B",
8112 "kind" : "video",
8113 "linkId" : "028E7FFC-FA84-47CF-8683-266C5E78C890",
8114 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
8115 "muted" : false,
8116 "newShot" : false,
8117 "speed" : 1,
8118 "srcIn" : 2414.6440677966107,
8119 "start" : 685.0847457627119,
8120 "track" : "v0"
8121 },
8122 {
8123 "duration" : 19.186440677966175,
8124 "fadeIn" : 0,
8125 "fadeOut" : 0,
8126 "id" : "36653084-A594-47D9-82C7-05282A42CE6C",
8127 "kind" : "audio",
8128 "linkId" : "028E7FFC-FA84-47CF-8683-266C5E78C890",
8129 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
8130 "muted" : true,
8131 "newShot" : false,
8132 "speed" : 1,
8133 "srcIn" : 2414.5601962552696,
8134 "start" : 685.0847457627119,
8135 "track" : "v1"
8136 },
8137 {
8138 "duration" : 19.186440677966175,
8139 "fadeIn" : 0,
8140 "fadeOut" : 0,
8141 "id" : "5A23146F-8246-498B-AC51-C4FE4B6C4DDA",
8142 "kind" : "audio",
8143 "linkId" : "028E7FFC-FA84-47CF-8683-266C5E78C890",
8144 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
8145 "muted" : false,
8146 "newShot" : false,
8147 "speed" : 1,
8148 "srcIn" : 2414.6068725585005,
8149 "start" : 685.0847457627119,
8150 "track" : "v2"
8151 },
8152 {
8153 "duration" : 19.186440677966175,
8154 "fadeIn" : 0,
8155 "fadeOut" : 0,
8156 "id" : "C7D8CAF3-5789-4F76-924A-24654359FD7F",
8157 "kind" : "video",
8158 "linkId" : "028E7FFC-FA84-47CF-8683-266C5E78C890",
8159 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
8160 "muted" : false,
8161 "newShot" : false,
8162 "speed" : 1,
8163 "srcIn" : 2414.4702654222724,
8164 "start" : 685.0847457627119,
8165 "track" : "v3"
8166 },
8167 {
8168 "duration" : 19.186440677966175,
8169 "fadeIn" : 0,
8170 "fadeOut" : 0,
8171 "id" : "DFB2B3EE-B824-4672-A351-269C20BF2ADE",
8172 "kind" : "video",
8173 "linkId" : "028E7FFC-FA84-47CF-8683-266C5E78C890",
8174 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
8175 "muted" : false,
8176 "newShot" : false,
8177 "speed" : 1,
8178 "srcIn" : 2414.4702608382736,
8179 "start" : 685.0847457627119,
8180 "track" : "v4"
8181 },
8182 {
8183 "duration" : 5.694915254237344,
8184 "fadeIn" : 0,
8185 "fadeOut" : 0,
8186 "id" : "EB2F9D36-DD5F-468C-9DA8-90D56D42E0A9",
8187 "kind" : "video",
8188 "linkId" : "DDA38179-F497-4A55-8BF0-09672B42936C",
8189 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
8190 "muted" : false,
8191 "newShot" : false,
8192 "speed" : 1,
8193 "srcIn" : 2446.1355932203396,
8194 "start" : 704.2711864406781,
8195 "track" : "v0"
8196 },
8197 {
8198 "duration" : 5.694915254237344,
8199 "fadeIn" : 0,
8200 "fadeOut" : 0,
8201 "id" : "D07C45CE-44A0-4FEF-8F34-54E4FD3F7D39",
8202 "kind" : "audio",
8203 "linkId" : "DDA38179-F497-4A55-8BF0-09672B42936C",
8204 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
8205 "muted" : true,
8206 "newShot" : false,
8207 "speed" : 1,
8208 "srcIn" : 2446.0517216789985,
8209 "start" : 704.2711864406781,
8210 "track" : "v1"
8211 },
8212 {
8213 "duration" : 5.694915254237344,
8214 "fadeIn" : 0,
8215 "fadeOut" : 0,
8216 "id" : "A22E3A44-8F76-4713-9D7F-7948A74D9CCC",
8217 "kind" : "audio",
8218 "linkId" : "DDA38179-F497-4A55-8BF0-09672B42936C",
8219 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
8220 "muted" : false,
8221 "newShot" : false,
8222 "speed" : 1,
8223 "srcIn" : 2446.0983979822295,
8224 "start" : 704.2711864406781,
8225 "track" : "v2"
8226 },
8227 {
8228 "duration" : 5.694915254237344,
8229 "fadeIn" : 0,
8230 "fadeOut" : 0,
8231 "id" : "15C53598-6F8F-4C89-B6EE-8BEAEA74F23A",
8232 "kind" : "video",
8233 "linkId" : "DDA38179-F497-4A55-8BF0-09672B42936C",
8234 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
8235 "muted" : false,
8236 "newShot" : false,
8237 "speed" : 1,
8238 "srcIn" : 2445.9617908460013,
8239 "start" : 704.2711864406781,
8240 "track" : "v3"
8241 },
8242 {
8243 "duration" : 5.694915254237344,
8244 "fadeIn" : 0,
8245 "fadeOut" : 0,
8246 "id" : "FB595553-A66D-4B22-8173-3AAE10CB58D0",
8247 "kind" : "video",
8248 "linkId" : "DDA38179-F497-4A55-8BF0-09672B42936C",
8249 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
8250 "muted" : false,
8251 "newShot" : false,
8252 "speed" : 1,
8253 "srcIn" : 2445.9617862620025,
8254 "start" : 704.2711864406781,
8255 "track" : "v4"
8256 },
8257 {
8258 "duration" : 9.59322033898286,
8259 "fadeIn" : 0,
8260 "fadeOut" : 0,
8261 "id" : "815EF8E2-F1E8-4B38-829B-D2C6971FDCD7",
8262 "kind" : "video",
8263 "linkId" : "1D15D7CC-5494-459C-BAF8-DD757B81547D",
8264 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
8265 "muted" : false,
8266 "newShot" : false,
8267 "speed" : 1,
8268 "srcIn" : 2454.1355932203396,
8269 "start" : 709.9661016949154,
8270 "track" : "v0"
8271 },
8272 {
8273 "duration" : 9.59322033898286,
8274 "fadeIn" : 0,
8275 "fadeOut" : 0,
8276 "id" : "6599B7E9-8BC7-4339-8544-580C7E40A6B3",
8277 "kind" : "audio",
8278 "linkId" : "1D15D7CC-5494-459C-BAF8-DD757B81547D",
8279 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
8280 "muted" : true,
8281 "newShot" : false,
8282 "speed" : 1,
8283 "srcIn" : 2454.0517216789985,
8284 "start" : 709.9661016949154,
8285 "track" : "v1"
8286 },
8287 {
8288 "duration" : 9.59322033898286,
8289 "fadeIn" : 0,
8290 "fadeOut" : 0,
8291 "id" : "913AD485-580A-4CF5-832E-3A866323A594",
8292 "kind" : "audio",
8293 "linkId" : "1D15D7CC-5494-459C-BAF8-DD757B81547D",
8294 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
8295 "muted" : false,
8296 "newShot" : false,
8297 "speed" : 1,
8298 "srcIn" : 2454.0983979822295,
8299 "start" : 709.9661016949154,
8300 "track" : "v2"
8301 },
8302 {
8303 "duration" : 9.59322033898286,
8304 "fadeIn" : 0,
8305 "fadeOut" : 0,
8306 "id" : "3F04DEF9-CD7C-486B-913A-989ECE77DE57",
8307 "kind" : "video",
8308 "linkId" : "1D15D7CC-5494-459C-BAF8-DD757B81547D",
8309 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
8310 "muted" : false,
8311 "newShot" : false,
8312 "speed" : 1,
8313 "srcIn" : 2453.9617908460013,
8314 "start" : 709.9661016949154,
8315 "track" : "v3"
8316 },
8317 {
8318 "duration" : 9.59322033898286,
8319 "fadeIn" : 0,
8320 "fadeOut" : 0,
8321 "id" : "715FA793-E1B3-4C26-B441-31D597B1938B",
8322 "kind" : "video",
8323 "linkId" : "1D15D7CC-5494-459C-BAF8-DD757B81547D",
8324 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
8325 "muted" : false,
8326 "newShot" : false,
8327 "speed" : 1,
8328 "srcIn" : 2453.9617862620025,
8329 "start" : 709.9661016949154,
8330 "track" : "v4"
8331 },
8332 {
8333 "duration" : 14.474576271186379,
8334 "fadeIn" : 0,
8335 "fadeOut" : 0,
8336 "id" : "40CCDD9D-5568-40CA-AEF5-90D94B8580BB",
8337 "kind" : "video",
8338 "linkId" : "F3271449-9FF2-4A35-8582-579E52CA2EFC",
8339 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
8340 "muted" : false,
8341 "newShot" : false,
8342 "speed" : 1,
8343 "srcIn" : 2526.6779661016953,
8344 "start" : 723.7627118644068,
8345 "track" : "v0"
8346 },
8347 {
8348 "duration" : 14.474576271186379,
8349 "fadeIn" : 0,
8350 "fadeOut" : 0,
8351 "id" : "C9DD81B8-F688-49A6-8638-2A626AF597C9",
8352 "kind" : "audio",
8353 "linkId" : "F3271449-9FF2-4A35-8582-579E52CA2EFC",
8354 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
8355 "muted" : true,
8356 "newShot" : false,
8357 "speed" : 1,
8358 "srcIn" : 2526.5940945603543,
8359 "start" : 723.7627118644068,
8360 "track" : "v1"
8361 },
8362 {
8363 "duration" : 14.474576271186379,
8364 "fadeIn" : 0,
8365 "fadeOut" : 0,
8366 "id" : "BC7A036D-1FA0-4738-A7F4-AD8EC3488B82",
8367 "kind" : "audio",
8368 "linkId" : "F3271449-9FF2-4A35-8582-579E52CA2EFC",
8369 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
8370 "muted" : false,
8371 "newShot" : false,
8372 "speed" : 1,
8373 "srcIn" : 2526.640770863585,
8374 "start" : 723.7627118644068,
8375 "track" : "v2"
8376 },
8377 {
8378 "duration" : 14.474576271186379,
8379 "fadeIn" : 0,
8380 "fadeOut" : 0,
8381 "id" : "A34F945B-0445-4497-9DD0-1E68E72ADA27",
8382 "kind" : "video",
8383 "linkId" : "F3271449-9FF2-4A35-8582-579E52CA2EFC",
8384 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
8385 "muted" : false,
8386 "newShot" : false,
8387 "speed" : 1,
8388 "srcIn" : 2526.504163727357,
8389 "start" : 723.7627118644068,
8390 "track" : "v3"
8391 },
8392 {
8393 "duration" : 14.474576271186379,
8394 "fadeIn" : 0,
8395 "fadeOut" : 0,
8396 "id" : "E2ECA174-0D6E-4E40-961D-0804BCB7E988",
8397 "kind" : "video",
8398 "linkId" : "F3271449-9FF2-4A35-8582-579E52CA2EFC",
8399 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
8400 "muted" : false,
8401 "newShot" : false,
8402 "speed" : 1,
8403 "srcIn" : 2526.5041591433583,
8404 "start" : 723.7627118644068,
8405 "track" : "v4"
8406 },
8407 {
8408 "duration" : 12.372881355932236,
8409 "fadeIn" : 0,
8410 "fadeOut" : 0,
8411 "id" : "793A00B4-D559-495E-BF6E-23E5FF440C18",
8412 "kind" : "video",
8413 "linkId" : "57734964-46B6-4D2E-8D3E-63DAA6A4B487",
8414 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
8415 "muted" : false,
8416 "newShot" : false,
8417 "speed" : 1,
8418 "srcIn" : 2542.203389830509,
8419 "start" : 738.2372881355932,
8420 "track" : "v0"
8421 },
8422 {
8423 "duration" : 12.372881355932236,
8424 "fadeIn" : 0,
8425 "fadeOut" : 0,
8426 "id" : "C4AC4AF2-ACA7-4B5B-B85A-13CC8E338B0C",
8427 "kind" : "audio",
8428 "linkId" : "57734964-46B6-4D2E-8D3E-63DAA6A4B487",
8429 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
8430 "muted" : true,
8431 "newShot" : false,
8432 "speed" : 1,
8433 "srcIn" : 2542.119518289168,
8434 "start" : 738.2372881355932,
8435 "track" : "v1"
8436 },
8437 {
8438 "duration" : 12.372881355932236,
8439 "fadeIn" : 0,
8440 "fadeOut" : 0,
8441 "id" : "0A904774-EBBE-42E8-BC32-42DDB4E21185",
8442 "kind" : "audio",
8443 "linkId" : "57734964-46B6-4D2E-8D3E-63DAA6A4B487",
8444 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
8445 "muted" : false,
8446 "newShot" : false,
8447 "speed" : 1,
8448 "srcIn" : 2542.166194592399,
8449 "start" : 738.2372881355932,
8450 "track" : "v2"
8451 },
8452 {
8453 "duration" : 12.372881355932236,
8454 "fadeIn" : 0,
8455 "fadeOut" : 0,
8456 "id" : "8CA5857B-2D5B-4E8A-90EB-7BE589C6785D",
8457 "kind" : "video",
8458 "linkId" : "57734964-46B6-4D2E-8D3E-63DAA6A4B487",
8459 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
8460 "muted" : false,
8461 "newShot" : false,
8462 "speed" : 1,
8463 "srcIn" : 2542.0295874561707,
8464 "start" : 738.2372881355932,
8465 "track" : "v3"
8466 },
8467 {
8468 "duration" : 12.372881355932236,
8469 "fadeIn" : 0,
8470 "fadeOut" : 0,
8471 "id" : "A1354766-A4E2-423C-A148-D457C645DEED",
8472 "kind" : "video",
8473 "linkId" : "57734964-46B6-4D2E-8D3E-63DAA6A4B487",
8474 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
8475 "muted" : false,
8476 "newShot" : false,
8477 "speed" : 1,
8478 "srcIn" : 2542.029582872172,
8479 "start" : 738.2372881355932,
8480 "track" : "v4"
8481 },
8482 {
8483 "duration" : 15112.237288,
8484 "fadeIn" : 0,
8485 "fadeOut" : 0,
8486 "id" : "1EDE863A-2897-4FFD-A49E-B943B438866F",
8487 "kind" : "video",
8488 "linkId" : "FD2B233D-ADAA-43D0-86FE-BD93A20C9129",
8489 "mediaId" : "CC300C55-7090-4A6E-81FA-55CB6644FAC9",
8490 "muted" : false,
8491 "newShot" : false,
8492 "speed" : 1,
8493 "srcIn" : 0,
8494 "start" : 4193.627118644067,
8495 "track" : "v0"
8496 },
8497 {
8498 "duration" : 15112.106667,
8499 "fadeIn" : 0,
8500 "fadeOut" : 0,
8501 "id" : "A3D2A572-1789-4532-9D15-1312C8312A41",
8502 "kind" : "audio",
8503 "linkId" : "FD2B233D-ADAA-43D0-86FE-BD93A20C9129",
8504 "mediaId" : "2613683E-12E6-402E-BF4D-6D9ACD7C1491",
8505 "muted" : true,
8506 "newShot" : false,
8507 "speed" : 1,
8508 "srcIn" : 0,
8509 "start" : 4193.710543352058,
8510 "track" : "v1"
8511 },
8512 {
8513 "duration" : 15112.213333,
8514 "fadeIn" : 0,
8515 "fadeOut" : 0,
8516 "id" : "387BD48F-5D42-4795-A2FE-DA7340E094B2",
8517 "kind" : "audio",
8518 "linkId" : "FD2B233D-ADAA-43D0-86FE-BD93A20C9129",
8519 "mediaId" : "7889E9EC-F67A-491B-81C0-0CF8338965A3",
8520 "muted" : false,
8521 "newShot" : false,
8522 "speed" : 1,
8523 "srcIn" : 0,
8524 "start" : 4193.660488258577,
8525 "track" : "v2"
8526 },
8527 {
8528 "duration" : 15112.066667,
8529 "fadeIn" : 0,
8530 "fadeOut" : 0,
8531 "id" : "15742468-AF06-408A-B0E3-DE8D543186D0",
8532 "kind" : "video",
8533 "linkId" : "FD2B233D-ADAA-43D0-86FE-BD93A20C9129",
8534 "mediaId" : "BC4E74C5-4C45-4633-B31A-3CAA58D4B2BF",
8535 "muted" : false,
8536 "newShot" : false,
8537 "speed" : 1,
8538 "srcIn" : 0,
8539 "start" : 4193.796015394061,
8540 "track" : "v3"
8541 },
8542 {
8543 "duration" : 15111.366667,
8544 "fadeIn" : 0,
8545 "fadeOut" : 0,
8546 "id" : "14D49BF0-68EB-4A4B-A627-9A55600AD1C2",
8547 "kind" : "video",
8548 "linkId" : "FD2B233D-ADAA-43D0-86FE-BD93A20C9129",
8549 "mediaId" : "98882264-A2ED-477B-A7B8-B63E54EDEACE",
8550 "muted" : false,
8551 "newShot" : false,
8552 "speed" : 1,
8553 "srcIn" : 0,
8554 "start" : 4193.796020644072,
8555 "track" : "v4"
8556 },
8557 {
8558 "duration" : 10045.254237,
8559 "fadeIn" : 0,
8560 "fadeOut" : 0,
8561 "id" : "82C83010-6BFE-4B26-81BC-CB291FAEB910",
8562 "kind" : "video",
8563 "linkId" : "F0D0664C-0131-446A-9354-CEB5E8E228DE",
8564 "mediaId" : "2D5EED57-87EB-44B4-9623-2CF48314C5F9",
8565 "muted" : false,
8566 "newShot" : false,
8567 "speed" : 1,
8568 "srcIn" : 0,
8569 "start" : 19305.864406779656,
8570 "track" : "v0"
8571 },
8572 {
8573 "duration" : 10045.162667,
8574 "fadeIn" : 0,
8575 "fadeOut" : 0,
8576 "id" : "83C1AFB5-E771-4D3A-8092-30A98A34B026",
8577 "kind" : "audio",
8578 "linkId" : "F0D0664C-0131-446A-9354-CEB5E8E228DE",
8579 "mediaId" : "0ECB1F4F-9065-432F-B0B1-7F47BD31FDCC",
8580 "muted" : false,
8581 "newShot" : false,
8582 "speed" : 1,
8583 "srcIn" : 0,
8584 "start" : 19305.9261039463,
8585 "track" : "v1"
8586 },
8587 {
8588 "duration" : 10045.248,
8589 "fadeIn" : 0,
8590 "fadeOut" : 0,
8591 "id" : "BD5B3686-815E-483F-8CDF-DC040625A81F",
8592 "kind" : "audio",
8593 "linkId" : "F0D0664C-0131-446A-9354-CEB5E8E228DE",
8594 "mediaId" : "02A5DFB3-9348-4102-BD13-F1856B431A70",
8595 "muted" : false,
8596 "newShot" : false,
8597 "speed" : 1,
8598 "srcIn" : 0,
8599 "start" : 19305.87683716514,
8600 "track" : "v2"
8601 },
8602 {
8603 "duration" : 10045.133333,
8604 "fadeIn" : 0,
8605 "fadeOut" : 0,
8606 "id" : "7C227E93-B4B2-44BB-81C4-0621CFA85F4E",
8607 "kind" : "video",
8608 "linkId" : "F0D0664C-0131-446A-9354-CEB5E8E228DE",
8609 "mediaId" : "9CA51332-5865-4C3D-9972-E781BDE75ECD",
8610 "muted" : false,
8611 "newShot" : false,
8612 "speed" : 1,
8613 "srcIn" : 0,
8614 "start" : 19306.0080157373,
8615 "track" : "v3"
8616 },
8617 {
8618 "duration" : 10045.166667,
8619 "fadeIn" : 0,
8620 "fadeOut" : 0,
8621 "id" : "472EB6D0-EF6A-469B-83F3-955C5C672361",
8622 "kind" : "video",
8623 "linkId" : "F0D0664C-0131-446A-9354-CEB5E8E228DE",
8624 "mediaId" : "0DEB0D7E-0C1E-49DA-9911-7773FB2158DE",
8625 "muted" : false,
8626 "newShot" : false,
8627 "speed" : 1,
8628 "srcIn" : 0,
8629 "start" : 19306.00801794631,
8630 "track" : "v4"
8631 },
8632 {
8633 "duration" : 11597.966102,
8634 "fadeIn" : 0,
8635 "fadeOut" : 0,
8636 "id" : "CE776B50-2A67-4285-9707-01DA14BBB04E",
8637 "kind" : "video",
8638 "linkId" : "F27C213D-566E-46A3-AF60-6D581EE67217",
8639 "mediaId" : "F06DF483-F96F-4DD1-8602-53609FA888F1",
8640 "muted" : false,
8641 "newShot" : false,
8642 "speed" : 1,
8643 "srcIn" : 0,
8644 "start" : 29351.18644067796,
8645 "track" : "v0"
8646 },
8647 {
8648 "duration" : 11598.101333,
8649 "fadeIn" : 0,
8650 "fadeOut" : 0,
8651 "id" : "7DB3CB28-8BB6-48ED-9B76-4FC6A5B2A106",
8652 "kind" : "audio",
8653 "linkId" : "F27C213D-566E-46A3-AF60-6D581EE67217",
8654 "mediaId" : "59213540-870A-4795-BD44-B3AFBC9F14E3",
8655 "muted" : false,
8656 "newShot" : false,
8657 "speed" : 1,
8658 "srcIn" : 0,
8659 "start" : 29351.035369510933,
8660 "track" : "v1"
8661 },
8662 {
8663 "duration" : 11598.464,
8664 "fadeIn" : 0,
8665 "fadeOut" : 0,
8666 "id" : "7941E3BC-CC88-41DB-A7BB-7C7519B8B9D6",
8667 "kind" : "audio",
8668 "linkId" : "F27C213D-566E-46A3-AF60-6D581EE67217",
8669 "mediaId" : "F4DB0AF3-2C74-4501-A58A-A519C046E3D6",
8670 "muted" : false,
8671 "newShot" : false,
8672 "speed" : 1,
8673 "srcIn" : 0,
8674 "start" : 29350.707797367297,
8675 "track" : "v2"
8676 },
8677 {
8678 "duration" : 11598.033333,
8679 "fadeIn" : 0,
8680 "fadeOut" : 0,
8681 "id" : "C6613E59-9E23-4934-B5DC-E224214F74D9",
8682 "kind" : "video",
8683 "linkId" : "F27C213D-566E-46A3-AF60-6D581EE67217",
8684 "mediaId" : "9F95D76B-EAB2-49AA-897A-C3C9A265E0CC",
8685 "muted" : false,
8686 "newShot" : false,
8687 "speed" : 1,
8688 "srcIn" : 0,
8689 "start" : 29351.122484093936,
8690 "track" : "v3"
8691 },
8692 {
8693 "duration" : 11598,
8694 "fadeIn" : 0,
8695 "fadeOut" : 0,
8696 "id" : "628E46F4-79BD-44D7-A964-903FBD21DD0F",
8697 "kind" : "video",
8698 "linkId" : "F27C213D-566E-46A3-AF60-6D581EE67217",
8699 "mediaId" : "CC497E03-E3E7-4283-A96C-F076D9F154A1",
8700 "muted" : false,
8701 "newShot" : false,
8702 "speed" : 1,
8703 "srcIn" : 0,
8704 "start" : 29351.12248226092,
8705 "track" : "v4"
8706 },
8707 {
8708 "duration" : 4.203389830508513,
8709 "fadeIn" : 0,
8710 "fadeOut" : 0,
8711 "id" : "5D2DD96A-1D28-4583-A85D-DDD1286F3EC1",
8712 "kind" : "video",
8713 "linkId" : "B7F43758-3B67-4E4D-8D71-152570BCA4AF",
8714 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
8715 "muted" : false,
8716 "newShot" : false,
8717 "speed" : 1,
8718 "srcIn" : 2491.0847457627124,
8719 "start" : 719.5593220338983,
8720 "track" : "v0"
8721 },
8722 {
8723 "duration" : 4.203389830508513,
8724 "fadeIn" : 0,
8725 "fadeOut" : 0,
8726 "id" : "B7181073-3D61-47D2-A83E-A7C19C246D68",
8727 "kind" : "audio",
8728 "linkId" : "B7F43758-3B67-4E4D-8D71-152570BCA4AF",
8729 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
8730 "muted" : true,
8731 "newShot" : false,
8732 "speed" : 1,
8733 "srcIn" : 2491.0008742213713,
8734 "start" : 719.5593220338983,
8735 "track" : "v1"
8736 },
8737 {
8738 "duration" : 4.203389830508513,
8739 "fadeIn" : 0,
8740 "fadeOut" : 0,
8741 "id" : "DF6474FD-D698-4232-9E94-EBE39BBB8545",
8742 "kind" : "audio",
8743 "linkId" : "B7F43758-3B67-4E4D-8D71-152570BCA4AF",
8744 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
8745 "muted" : false,
8746 "newShot" : false,
8747 "speed" : 1,
8748 "srcIn" : 2491.0475505246022,
8749 "start" : 719.5593220338983,
8750 "track" : "v2"
8751 },
8752 {
8753 "duration" : 4.203389830508513,
8754 "fadeIn" : 0,
8755 "fadeOut" : 0,
8756 "id" : "D06A7AA2-0BC5-4031-9EDA-7A81673249DF",
8757 "kind" : "video",
8758 "linkId" : "B7F43758-3B67-4E4D-8D71-152570BCA4AF",
8759 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
8760 "muted" : false,
8761 "newShot" : false,
8762 "speed" : 1,
8763 "srcIn" : 2490.910943388374,
8764 "start" : 719.5593220338983,
8765 "track" : "v3"
8766 },
8767 {
8768 "duration" : 4.203389830508513,
8769 "fadeIn" : 0,
8770 "fadeOut" : 0,
8771 "id" : "39DCC5F4-B663-454C-BD55-6E675B931DBA",
8772 "kind" : "video",
8773 "linkId" : "B7F43758-3B67-4E4D-8D71-152570BCA4AF",
8774 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
8775 "muted" : false,
8776 "newShot" : false,
8777 "speed" : 1,
8778 "srcIn" : 2490.9109388043753,
8779 "start" : 719.5593220338983,
8780 "track" : "v4"
8781 },
8782 {
8783 "duration" : 15.152542372881271,
8784 "fadeIn" : 0,
8785 "fadeOut" : 0,
8786 "id" : "F4373D19-EA06-4C7F-A620-FC8FD1BD6AE3",
8787 "kind" : "video",
8788 "linkId" : "DF0D7986-17C5-4CB7-B683-DD878723E8C7",
8789 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
8790 "muted" : false,
8791 "newShot" : false,
8792 "speed" : 1,
8793 "srcIn" : 2557.3898305084754,
8794 "start" : 750.6101694915254,
8795 "track" : "v0"
8796 },
8797 {
8798 "duration" : 15.152542372881271,
8799 "fadeIn" : 0,
8800 "fadeOut" : 0,
8801 "id" : "84DE87BB-60F8-41DF-933A-3B41704FE3D2",
8802 "kind" : "audio",
8803 "linkId" : "DF0D7986-17C5-4CB7-B683-DD878723E8C7",
8804 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
8805 "muted" : true,
8806 "newShot" : false,
8807 "speed" : 1,
8808 "srcIn" : 2557.3059589671343,
8809 "start" : 750.6101694915254,
8810 "track" : "v1"
8811 },
8812 {
8813 "duration" : 15.152542372881271,
8814 "fadeIn" : 0,
8815 "fadeOut" : 0,
8816 "id" : "F6A29700-D4EE-4106-AA51-21C13AD34CA4",
8817 "kind" : "audio",
8818 "linkId" : "DF0D7986-17C5-4CB7-B683-DD878723E8C7",
8819 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
8820 "muted" : false,
8821 "newShot" : false,
8822 "speed" : 1,
8823 "srcIn" : 2557.352635270365,
8824 "start" : 750.6101694915254,
8825 "track" : "v2"
8826 },
8827 {
8828 "duration" : 15.152542372881271,
8829 "fadeIn" : 0,
8830 "fadeOut" : 0,
8831 "id" : "353D8752-627E-40D9-8D76-BFB4084F392D",
8832 "kind" : "video",
8833 "linkId" : "DF0D7986-17C5-4CB7-B683-DD878723E8C7",
8834 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
8835 "muted" : false,
8836 "newShot" : false,
8837 "speed" : 1,
8838 "srcIn" : 2557.216028134137,
8839 "start" : 750.6101694915254,
8840 "track" : "v3"
8841 },
8842 {
8843 "duration" : 15.152542372881271,
8844 "fadeIn" : 0,
8845 "fadeOut" : 0,
8846 "id" : "901CEC11-981C-4F2C-8857-98E171F95538",
8847 "kind" : "video",
8848 "linkId" : "DF0D7986-17C5-4CB7-B683-DD878723E8C7",
8849 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
8850 "muted" : false,
8851 "newShot" : false,
8852 "speed" : 1,
8853 "srcIn" : 2557.2160235501383,
8854 "start" : 750.6101694915254,
8855 "track" : "v4"
8856 },
8857 {
8858 "duration" : 5.661016949152554,
8859 "fadeIn" : 0,
8860 "fadeOut" : 0,
8861 "id" : "E259582D-FC1C-4FA5-BE66-998C83FEC210",
8862 "kind" : "video",
8863 "linkId" : "9A0BE697-0A09-4342-804A-A10695A5765B",
8864 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
8865 "muted" : false,
8866 "newShot" : false,
8867 "speed" : 1,
8868 "srcIn" : 2575.42372881356,
8869 "start" : 765.7627118644068,
8870 "track" : "v0"
8871 },
8872 {
8873 "duration" : 5.661016949152554,
8874 "fadeIn" : 0,
8875 "fadeOut" : 0,
8876 "id" : "FB4E9075-64CB-4C65-A7BE-659E4788FCF8",
8877 "kind" : "audio",
8878 "linkId" : "9A0BE697-0A09-4342-804A-A10695A5765B",
8879 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
8880 "muted" : true,
8881 "newShot" : false,
8882 "speed" : 1,
8883 "srcIn" : 2575.339857272219,
8884 "start" : 765.7627118644068,
8885 "track" : "v1"
8886 },
8887 {
8888 "duration" : 5.661016949152554,
8889 "fadeIn" : 0,
8890 "fadeOut" : 0,
8891 "id" : "A6714A57-F046-4DE6-805C-A525B611FBD7",
8892 "kind" : "audio",
8893 "linkId" : "9A0BE697-0A09-4342-804A-A10695A5765B",
8894 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
8895 "muted" : false,
8896 "newShot" : false,
8897 "speed" : 1,
8898 "srcIn" : 2575.38653357545,
8899 "start" : 765.7627118644068,
8900 "track" : "v2"
8901 },
8902 {
8903 "duration" : 5.661016949152554,
8904 "fadeIn" : 0,
8905 "fadeOut" : 0,
8906 "id" : "DB4E1C97-4EDB-41E2-966E-057D14890226",
8907 "kind" : "video",
8908 "linkId" : "9A0BE697-0A09-4342-804A-A10695A5765B",
8909 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
8910 "muted" : false,
8911 "newShot" : false,
8912 "speed" : 1,
8913 "srcIn" : 2575.249926439222,
8914 "start" : 765.7627118644068,
8915 "track" : "v3"
8916 },
8917 {
8918 "duration" : 5.661016949152554,
8919 "fadeIn" : 0,
8920 "fadeOut" : 0,
8921 "id" : "E9D9C86D-9ED7-4309-8C45-79EE9034CFD1",
8922 "kind" : "video",
8923 "linkId" : "9A0BE697-0A09-4342-804A-A10695A5765B",
8924 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
8925 "muted" : false,
8926 "newShot" : false,
8927 "speed" : 1,
8928 "srcIn" : 2575.249921855223,
8929 "start" : 765.7627118644068,
8930 "track" : "v4"
8931 },
8932 {
8933 "duration" : 65.15254237288138,
8934 "fadeIn" : 0,
8935 "fadeOut" : 0,
8936 "id" : "864CC0F8-B363-4CE4-B255-61F2E32173FE",
8937 "kind" : "video",
8938 "linkId" : "FAD43732-5E8B-4F4A-AF1A-5F977F88D803",
8939 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
8940 "muted" : false,
8941 "newShot" : false,
8942 "speed" : 1,
8943 "srcIn" : 2581.0847457627124,
8944 "start" : 778.0677966101695,
8945 "track" : "v0"
8946 },
8947 {
8948 "duration" : 65.15254237288138,
8949 "fadeIn" : 0,
8950 "fadeOut" : 0,
8951 "id" : "F19656B8-AD96-45A9-AB45-41E84163B720",
8952 "kind" : "audio",
8953 "linkId" : "FAD43732-5E8B-4F4A-AF1A-5F977F88D803",
8954 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
8955 "muted" : true,
8956 "newShot" : false,
8957 "speed" : 1,
8958 "srcIn" : 2581.0008742213713,
8959 "start" : 778.0677966101695,
8960 "track" : "v1"
8961 },
8962 {
8963 "duration" : 65.15254237288138,
8964 "fadeIn" : 0,
8965 "fadeOut" : 0,
8966 "id" : "D40E7E5C-E58F-4D04-8A61-4C5A0AFE2C2C",
8967 "kind" : "audio",
8968 "linkId" : "FAD43732-5E8B-4F4A-AF1A-5F977F88D803",
8969 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
8970 "muted" : false,
8971 "newShot" : false,
8972 "speed" : 1,
8973 "srcIn" : 2581.0475505246022,
8974 "start" : 778.0677966101695,
8975 "track" : "v2"
8976 },
8977 {
8978 "duration" : 65.15254237288138,
8979 "fadeIn" : 0,
8980 "fadeOut" : 0,
8981 "id" : "0AEACF86-70AC-4999-8D26-F9BDE6003C4F",
8982 "kind" : "video",
8983 "linkId" : "FAD43732-5E8B-4F4A-AF1A-5F977F88D803",
8984 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
8985 "muted" : false,
8986 "newShot" : false,
8987 "speed" : 1,
8988 "srcIn" : 2580.910943388374,
8989 "start" : 778.0677966101695,
8990 "track" : "v3"
8991 },
8992 {
8993 "duration" : 65.15254237288138,
8994 "fadeIn" : 0,
8995 "fadeOut" : 0,
8996 "id" : "8566FA67-6A70-4CFF-9C03-5DED0AE5F620",
8997 "kind" : "video",
8998 "linkId" : "FAD43732-5E8B-4F4A-AF1A-5F977F88D803",
8999 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
9000 "muted" : false,
9001 "newShot" : false,
9002 "speed" : 1,
9003 "srcIn" : 2580.9109388043753,
9004 "start" : 778.0677966101695,
9005 "track" : "v4"
9006 },
9007 {
9008 "duration" : 2.135593220338933,
9009 "fadeIn" : 0,
9010 "fadeOut" : 0,
9011 "id" : "0FAA1CB7-A092-406F-B1FD-828EE4768B07",
9012 "kind" : "video",
9013 "linkId" : "FC57D67A-F7BF-448D-985C-05D71447DD0A",
9014 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
9015 "muted" : false,
9016 "newShot" : false,
9017 "speed" : 1,
9018 "srcIn" : 2646.2372881355936,
9019 "start" : 843.2203389830509,
9020 "track" : "v0"
9021 },
9022 {
9023 "duration" : 2.135593220338933,
9024 "fadeIn" : 0,
9025 "fadeOut" : 0,
9026 "id" : "AC5A9D19-E998-4CC0-B033-5C9AF84326C4",
9027 "kind" : "audio",
9028 "linkId" : "FC57D67A-F7BF-448D-985C-05D71447DD0A",
9029 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
9030 "muted" : true,
9031 "newShot" : false,
9032 "speed" : 1,
9033 "srcIn" : 2646.1534165942526,
9034 "start" : 843.2203389830509,
9035 "track" : "v1"
9036 },
9037 {
9038 "duration" : 2.135593220338933,
9039 "fadeIn" : 0,
9040 "fadeOut" : 0,
9041 "id" : "BF2AE534-1A06-4009-B825-8C1570D790C7",
9042 "kind" : "audio",
9043 "linkId" : "FC57D67A-F7BF-448D-985C-05D71447DD0A",
9044 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
9045 "muted" : false,
9046 "newShot" : false,
9047 "speed" : 1,
9048 "srcIn" : 2646.2000928974835,
9049 "start" : 843.2203389830509,
9050 "track" : "v2"
9051 },
9052 {
9053 "duration" : 2.135593220338933,
9054 "fadeIn" : 0,
9055 "fadeOut" : 0,
9056 "id" : "10E9AD27-80E1-4F3C-BD87-414A1B6617A8",
9057 "kind" : "video",
9058 "linkId" : "FC57D67A-F7BF-448D-985C-05D71447DD0A",
9059 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
9060 "muted" : false,
9061 "newShot" : false,
9062 "speed" : 1,
9063 "srcIn" : 2646.0634857612554,
9064 "start" : 843.2203389830509,
9065 "track" : "v3"
9066 },
9067 {
9068 "duration" : 2.135593220338933,
9069 "fadeIn" : 0,
9070 "fadeOut" : 0,
9071 "id" : "3EE49ACC-C52A-4ADF-86BE-884CE4259D4B",
9072 "kind" : "video",
9073 "linkId" : "FC57D67A-F7BF-448D-985C-05D71447DD0A",
9074 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
9075 "muted" : false,
9076 "newShot" : false,
9077 "speed" : 1,
9078 "srcIn" : 2646.0634811772566,
9079 "start" : 843.2203389830509,
9080 "track" : "v4"
9081 },
9082 {
9083 "duration" : 2.1355932203390466,
9084 "fadeIn" : 0,
9085 "fadeOut" : 0,
9086 "id" : "6F6DB3B3-42CE-4046-9988-087B5847CE7F",
9087 "kind" : "video",
9088 "linkId" : "28EA7312-79F0-4C9C-A37D-04E32716FA29",
9089 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
9090 "muted" : false,
9091 "newShot" : false,
9092 "speed" : 1,
9093 "srcIn" : 2650.203389830509,
9094 "start" : 845.3559322033898,
9095 "track" : "v0"
9096 },
9097 {
9098 "duration" : 2.1355932203390466,
9099 "fadeIn" : 0,
9100 "fadeOut" : 0,
9101 "id" : "96066F14-B10E-4826-890D-95FE249BB166",
9102 "kind" : "audio",
9103 "linkId" : "28EA7312-79F0-4C9C-A37D-04E32716FA29",
9104 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
9105 "muted" : true,
9106 "newShot" : false,
9107 "speed" : 1,
9108 "srcIn" : 2650.119518289168,
9109 "start" : 845.3559322033898,
9110 "track" : "v1"
9111 },
9112 {
9113 "duration" : 2.1355932203390466,
9114 "fadeIn" : 0,
9115 "fadeOut" : 0,
9116 "id" : "B07978FC-00CF-41C2-8657-665D128435D0",
9117 "kind" : "audio",
9118 "linkId" : "28EA7312-79F0-4C9C-A37D-04E32716FA29",
9119 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
9120 "muted" : false,
9121 "newShot" : false,
9122 "speed" : 1,
9123 "srcIn" : 2650.166194592399,
9124 "start" : 845.3559322033898,
9125 "track" : "v2"
9126 },
9127 {
9128 "duration" : 2.1355932203390466,
9129 "fadeIn" : 0,
9130 "fadeOut" : 0,
9131 "id" : "4FAD2B83-A8E8-4692-9360-C2F908C32F76",
9132 "kind" : "video",
9133 "linkId" : "28EA7312-79F0-4C9C-A37D-04E32716FA29",
9134 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
9135 "muted" : false,
9136 "newShot" : false,
9137 "speed" : 1,
9138 "srcIn" : 2650.0295874561707,
9139 "start" : 845.3559322033898,
9140 "track" : "v3"
9141 },
9142 {
9143 "duration" : 2.1355932203390466,
9144 "fadeIn" : 0,
9145 "fadeOut" : 0,
9146 "id" : "DF197166-8983-45B6-A285-54D45DECCFFB",
9147 "kind" : "video",
9148 "linkId" : "28EA7312-79F0-4C9C-A37D-04E32716FA29",
9149 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
9150 "muted" : false,
9151 "newShot" : false,
9152 "speed" : 1,
9153 "srcIn" : 2650.029582872172,
9154 "start" : 845.3559322033898,
9155 "track" : "v4"
9156 },
9157 {
9158 "duration" : 0.7118644067796822,
9159 "fadeIn" : 0,
9160 "fadeOut" : 0,
9161 "id" : "A6D0B507-6A36-4A23-B415-3888A8D66CEE",
9162 "kind" : "video",
9163 "linkId" : "E3D0D877-F5B9-4B99-B94B-44E5E47BEAE8",
9164 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
9165 "muted" : false,
9166 "newShot" : false,
9167 "speed" : 1,
9168 "srcIn" : 2653.118644067797,
9169 "start" : 847.4915254237288,
9170 "track" : "v0"
9171 },
9172 {
9173 "duration" : 0.7118644067796822,
9174 "fadeIn" : 0,
9175 "fadeOut" : 0,
9176 "id" : "AEBE3D73-9EE9-42F3-80FF-AF4E272C5BF5",
9177 "kind" : "audio",
9178 "linkId" : "E3D0D877-F5B9-4B99-B94B-44E5E47BEAE8",
9179 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
9180 "muted" : true,
9181 "newShot" : false,
9182 "speed" : 1,
9183 "srcIn" : 2653.034772526456,
9184 "start" : 847.4915254237288,
9185 "track" : "v1"
9186 },
9187 {
9188 "duration" : 0.7118644067796822,
9189 "fadeIn" : 0,
9190 "fadeOut" : 0,
9191 "id" : "A3B22B00-BA4B-4A40-91A3-F3E3C884C9BA",
9192 "kind" : "audio",
9193 "linkId" : "E3D0D877-F5B9-4B99-B94B-44E5E47BEAE8",
9194 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
9195 "muted" : false,
9196 "newShot" : false,
9197 "speed" : 1,
9198 "srcIn" : 2653.081448829687,
9199 "start" : 847.4915254237288,
9200 "track" : "v2"
9201 },
9202 {
9203 "duration" : 0.7118644067796822,
9204 "fadeIn" : 0,
9205 "fadeOut" : 0,
9206 "id" : "7B3ADFE8-7F47-4800-B8D6-77B5B07AFA99",
9207 "kind" : "video",
9208 "linkId" : "E3D0D877-F5B9-4B99-B94B-44E5E47BEAE8",
9209 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
9210 "muted" : false,
9211 "newShot" : false,
9212 "speed" : 1,
9213 "srcIn" : 2652.944841693459,
9214 "start" : 847.4915254237288,
9215 "track" : "v3"
9216 },
9217 {
9218 "duration" : 0.7118644067796822,
9219 "fadeIn" : 0,
9220 "fadeOut" : 0,
9221 "id" : "BC37902D-B6DE-4EE5-9996-467DEC167330",
9222 "kind" : "video",
9223 "linkId" : "E3D0D877-F5B9-4B99-B94B-44E5E47BEAE8",
9224 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
9225 "muted" : false,
9226 "newShot" : false,
9227 "speed" : 1,
9228 "srcIn" : 2652.94483710946,
9229 "start" : 847.4915254237288,
9230 "track" : "v4"
9231 },
9232 {
9233 "duration" : 0.7796610169491487,
9234 "fadeIn" : 0,
9235 "fadeOut" : 0,
9236 "id" : "8C61A3D8-55F3-4CCD-A02E-859B57EDA9CD",
9237 "kind" : "video",
9238 "linkId" : "86F71C9D-4CC8-49AE-85D6-08C4B076CE87",
9239 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
9240 "muted" : false,
9241 "newShot" : false,
9242 "speed" : 1,
9243 "srcIn" : 2658.71186440678,
9244 "start" : 848.2033898305085,
9245 "track" : "v0"
9246 },
9247 {
9248 "duration" : 0.7796610169491487,
9249 "fadeIn" : 0,
9250 "fadeOut" : 0,
9251 "id" : "25E99D83-FDE9-4E70-BBD3-A76D726F2D15",
9252 "kind" : "audio",
9253 "linkId" : "86F71C9D-4CC8-49AE-85D6-08C4B076CE87",
9254 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
9255 "muted" : true,
9256 "newShot" : false,
9257 "speed" : 1,
9258 "srcIn" : 2658.627992865439,
9259 "start" : 848.2033898305085,
9260 "track" : "v1"
9261 },
9262 {
9263 "duration" : 0.7796610169491487,
9264 "fadeIn" : 0,
9265 "fadeOut" : 0,
9266 "id" : "5FD3B500-E4B2-4AE8-B37D-DE251B465DBD",
9267 "kind" : "audio",
9268 "linkId" : "86F71C9D-4CC8-49AE-85D6-08C4B076CE87",
9269 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
9270 "muted" : false,
9271 "newShot" : false,
9272 "speed" : 1,
9273 "srcIn" : 2658.67466916867,
9274 "start" : 848.2033898305085,
9275 "track" : "v2"
9276 },
9277 {
9278 "duration" : 0.7796610169491487,
9279 "fadeIn" : 0,
9280 "fadeOut" : 0,
9281 "id" : "7BE9FE0B-5C6E-49DC-ACD3-613EE443EF5B",
9282 "kind" : "video",
9283 "linkId" : "86F71C9D-4CC8-49AE-85D6-08C4B076CE87",
9284 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
9285 "muted" : false,
9286 "newShot" : false,
9287 "speed" : 1,
9288 "srcIn" : 2658.5380620324418,
9289 "start" : 848.2033898305085,
9290 "track" : "v3"
9291 },
9292 {
9293 "duration" : 0.7796610169491487,
9294 "fadeIn" : 0,
9295 "fadeOut" : 0,
9296 "id" : "E51F2531-CEDB-4175-A047-516CD3142C93",
9297 "kind" : "video",
9298 "linkId" : "86F71C9D-4CC8-49AE-85D6-08C4B076CE87",
9299 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
9300 "muted" : false,
9301 "newShot" : false,
9302 "speed" : 1,
9303 "srcIn" : 2658.538057448443,
9304 "start" : 848.2033898305085,
9305 "track" : "v4"
9306 },
9307 {
9308 "duration" : 4.881355932203405,
9309 "fadeIn" : 0,
9310 "fadeOut" : 0,
9311 "id" : "352DB16B-39D1-475A-8C95-A0D712C61DA1",
9312 "kind" : "video",
9313 "linkId" : "3EABBA0E-4C24-4221-9E58-79D35BC9B08D",
9314 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
9315 "muted" : false,
9316 "newShot" : false,
9317 "speed" : 1,
9318 "srcIn" : 2672.77966101695,
9319 "start" : 848.9830508474577,
9320 "track" : "v0"
9321 },
9322 {
9323 "duration" : 4.881355932203405,
9324 "fadeIn" : 0,
9325 "fadeOut" : 0,
9326 "id" : "8904F908-4ED1-49D6-A064-B85CD9585F94",
9327 "kind" : "audio",
9328 "linkId" : "3EABBA0E-4C24-4221-9E58-79D35BC9B08D",
9329 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
9330 "muted" : true,
9331 "newShot" : false,
9332 "speed" : 1,
9333 "srcIn" : 2672.6957894756088,
9334 "start" : 848.9830508474577,
9335 "track" : "v1"
9336 },
9337 {
9338 "duration" : 4.881355932203405,
9339 "fadeIn" : 0,
9340 "fadeOut" : 0,
9341 "id" : "F88FC5BF-C07B-4659-96CD-F0C6423F09A3",
9342 "kind" : "audio",
9343 "linkId" : "3EABBA0E-4C24-4221-9E58-79D35BC9B08D",
9344 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
9345 "muted" : false,
9346 "newShot" : false,
9347 "speed" : 1,
9348 "srcIn" : 2672.7424657788397,
9349 "start" : 848.9830508474577,
9350 "track" : "v2"
9351 },
9352 {
9353 "duration" : 4.881355932203405,
9354 "fadeIn" : 0,
9355 "fadeOut" : 0,
9356 "id" : "319CEC14-6215-4445-8F0D-91C308A3420E",
9357 "kind" : "video",
9358 "linkId" : "3EABBA0E-4C24-4221-9E58-79D35BC9B08D",
9359 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
9360 "muted" : false,
9361 "newShot" : false,
9362 "speed" : 1,
9363 "srcIn" : 2672.6058586426116,
9364 "start" : 848.9830508474577,
9365 "track" : "v3"
9366 },
9367 {
9368 "duration" : 4.881355932203405,
9369 "fadeIn" : 0,
9370 "fadeOut" : 0,
9371 "id" : "EE17ADAE-984B-467F-8219-EC6C5F5D962B",
9372 "kind" : "video",
9373 "linkId" : "3EABBA0E-4C24-4221-9E58-79D35BC9B08D",
9374 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
9375 "muted" : false,
9376 "newShot" : false,
9377 "speed" : 1,
9378 "srcIn" : 2672.6058540586127,
9379 "start" : 848.9830508474577,
9380 "track" : "v4"
9381 },
9382 {
9383 "duration" : 4.101694915254257,
9384 "fadeIn" : 0,
9385 "fadeOut" : 0,
9386 "id" : "FCB1D834-DEAA-473C-A554-9F80416158C2",
9387 "kind" : "video",
9388 "linkId" : "3B4E4A04-CB5C-4738-B65E-F7076495C9D8",
9389 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
9390 "muted" : false,
9391 "newShot" : false,
9392 "speed" : 1,
9393 "srcIn" : 2710.7118644067805,
9394 "start" : 853.8644067796611,
9395 "track" : "v0"
9396 },
9397 {
9398 "duration" : 4.101694915254257,
9399 "fadeIn" : 0,
9400 "fadeOut" : 0,
9401 "id" : "881E7DB1-B037-40A9-9296-A99F279D7B54",
9402 "kind" : "audio",
9403 "linkId" : "3B4E4A04-CB5C-4738-B65E-F7076495C9D8",
9404 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
9405 "muted" : true,
9406 "newShot" : false,
9407 "speed" : 1,
9408 "srcIn" : 2710.6279928654394,
9409 "start" : 853.8644067796611,
9410 "track" : "v1"
9411 },
9412 {
9413 "duration" : 4.101694915254257,
9414 "fadeIn" : 0,
9415 "fadeOut" : 0,
9416 "id" : "5D38E6BE-385D-45A3-9AF3-30FF2BB3F39B",
9417 "kind" : "audio",
9418 "linkId" : "3B4E4A04-CB5C-4738-B65E-F7076495C9D8",
9419 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
9420 "muted" : false,
9421 "newShot" : false,
9422 "speed" : 1,
9423 "srcIn" : 2710.6746691686703,
9424 "start" : 853.8644067796611,
9425 "track" : "v2"
9426 },
9427 {
9428 "duration" : 4.101694915254257,
9429 "fadeIn" : 0,
9430 "fadeOut" : 0,
9431 "id" : "7B349858-7AE2-48A3-991E-DB25F74D2807",
9432 "kind" : "video",
9433 "linkId" : "3B4E4A04-CB5C-4738-B65E-F7076495C9D8",
9434 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
9435 "muted" : false,
9436 "newShot" : false,
9437 "speed" : 1,
9438 "srcIn" : 2710.538062032442,
9439 "start" : 853.8644067796611,
9440 "track" : "v3"
9441 },
9442 {
9443 "duration" : 4.101694915254257,
9444 "fadeIn" : 0,
9445 "fadeOut" : 0,
9446 "id" : "58E4F08F-1403-43B7-B93B-C6BFA5127F8D",
9447 "kind" : "video",
9448 "linkId" : "3B4E4A04-CB5C-4738-B65E-F7076495C9D8",
9449 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
9450 "muted" : false,
9451 "newShot" : false,
9452 "speed" : 1,
9453 "srcIn" : 2710.5380574484434,
9454 "start" : 853.8644067796611,
9455 "track" : "v4"
9456 },
9457 {
9458 "duration" : 30.27118644067798,
9459 "fadeIn" : 0,
9460 "fadeOut" : 0,
9461 "id" : "52F2190F-1C89-44EA-94D3-8A7085B8172A",
9462 "kind" : "video",
9463 "linkId" : "AE414DA9-138E-4B31-BCEA-EBD522E81175",
9464 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
9465 "muted" : false,
9466 "newShot" : false,
9467 "speed" : 1,
9468 "srcIn" : 2721.322033898306,
9469 "start" : 857.9661016949152,
9470 "track" : "v0"
9471 },
9472 {
9473 "duration" : 30.27118644067798,
9474 "fadeIn" : 0,
9475 "fadeOut" : 0,
9476 "id" : "369AF7CA-F05E-4BAF-B4DA-92398E680470",
9477 "kind" : "audio",
9478 "linkId" : "AE414DA9-138E-4B31-BCEA-EBD522E81175",
9479 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
9480 "muted" : true,
9481 "newShot" : false,
9482 "speed" : 1,
9483 "srcIn" : 2721.238162356965,
9484 "start" : 857.9661016949152,
9485 "track" : "v1"
9486 },
9487 {
9488 "duration" : 30.27118644067798,
9489 "fadeIn" : 0,
9490 "fadeOut" : 0,
9491 "id" : "E0C2832B-D290-4F69-B311-6CCB13D81611",
9492 "kind" : "audio",
9493 "linkId" : "AE414DA9-138E-4B31-BCEA-EBD522E81175",
9494 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
9495 "muted" : false,
9496 "newShot" : false,
9497 "speed" : 1,
9498 "srcIn" : 2721.284838660196,
9499 "start" : 857.9661016949152,
9500 "track" : "v2"
9501 },
9502 {
9503 "duration" : 30.27118644067798,
9504 "fadeIn" : 0,
9505 "fadeOut" : 0,
9506 "id" : "435A5598-D5FB-4880-AF72-ACF1883BA634",
9507 "kind" : "video",
9508 "linkId" : "AE414DA9-138E-4B31-BCEA-EBD522E81175",
9509 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
9510 "muted" : false,
9511 "newShot" : false,
9512 "speed" : 1,
9513 "srcIn" : 2721.1482315239678,
9514 "start" : 857.9661016949152,
9515 "track" : "v3"
9516 },
9517 {
9518 "duration" : 30.27118644067798,
9519 "fadeIn" : 0,
9520 "fadeOut" : 0,
9521 "id" : "E1A300BD-01C6-44F8-A372-7EDEAAA3407A",
9522 "kind" : "video",
9523 "linkId" : "AE414DA9-138E-4B31-BCEA-EBD522E81175",
9524 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
9525 "muted" : false,
9526 "newShot" : false,
9527 "speed" : 1,
9528 "srcIn" : 2721.148226939969,
9529 "start" : 857.9661016949152,
9530 "track" : "v4"
9531 },
9532 {
9533 "duration" : 2.8135593220339388,
9534 "fadeIn" : 0,
9535 "fadeOut" : 0,
9536 "id" : "F79BA8C9-1EEA-4EAD-9408-BEB36A06F6EC",
9537 "kind" : "video",
9538 "linkId" : "4045FA44-3B4D-4BFC-A67F-515E3C553BA0",
9539 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
9540 "muted" : false,
9541 "newShot" : false,
9542 "speed" : 1,
9543 "srcIn" : 2760.915254237289,
9544 "start" : 888.2372881355932,
9545 "track" : "v0"
9546 },
9547 {
9548 "duration" : 2.8135593220339388,
9549 "fadeIn" : 0,
9550 "fadeOut" : 0,
9551 "id" : "E4CC2092-0AFA-454F-A056-DC1D4B96EAE3",
9552 "kind" : "audio",
9553 "linkId" : "4045FA44-3B4D-4BFC-A67F-515E3C553BA0",
9554 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
9555 "muted" : true,
9556 "newShot" : false,
9557 "speed" : 1,
9558 "srcIn" : 2760.831382695948,
9559 "start" : 888.2372881355932,
9560 "track" : "v1"
9561 },
9562 {
9563 "duration" : 2.8135593220339388,
9564 "fadeIn" : 0,
9565 "fadeOut" : 0,
9566 "id" : "C6569D08-E232-40FD-AD5E-38A850503FDF",
9567 "kind" : "audio",
9568 "linkId" : "4045FA44-3B4D-4BFC-A67F-515E3C553BA0",
9569 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
9570 "muted" : false,
9571 "newShot" : false,
9572 "speed" : 1,
9573 "srcIn" : 2760.878058999179,
9574 "start" : 888.2372881355932,
9575 "track" : "v2"
9576 },
9577 {
9578 "duration" : 2.8135593220339388,
9579 "fadeIn" : 0,
9580 "fadeOut" : 0,
9581 "id" : "5D424C43-D60A-4A52-A33B-BBEB380053F2",
9582 "kind" : "video",
9583 "linkId" : "4045FA44-3B4D-4BFC-A67F-515E3C553BA0",
9584 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
9585 "muted" : false,
9586 "newShot" : false,
9587 "speed" : 1,
9588 "srcIn" : 2760.7414518629507,
9589 "start" : 888.2372881355932,
9590 "track" : "v3"
9591 },
9592 {
9593 "duration" : 2.8135593220339388,
9594 "fadeIn" : 0,
9595 "fadeOut" : 0,
9596 "id" : "C9ECC1B1-1D5A-4D84-9012-9864635A322A",
9597 "kind" : "video",
9598 "linkId" : "4045FA44-3B4D-4BFC-A67F-515E3C553BA0",
9599 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
9600 "muted" : false,
9601 "newShot" : false,
9602 "speed" : 1,
9603 "srcIn" : 2760.741447278952,
9604 "start" : 888.2372881355932,
9605 "track" : "v4"
9606 },
9607 {
9608 "duration" : 5.288135593220318,
9609 "fadeIn" : 0,
9610 "fadeOut" : 0,
9611 "id" : "1B56AEBA-0FE3-40D6-A485-407844047F55",
9612 "kind" : "video",
9613 "linkId" : "F572F9CE-FD52-495A-A477-9D6D62AC1086",
9614 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
9615 "muted" : false,
9616 "newShot" : false,
9617 "speed" : 1,
9618 "srcIn" : 2783.186440677967,
9619 "start" : 891.0508474576271,
9620 "track" : "v0"
9621 },
9622 {
9623 "duration" : 5.288135593220318,
9624 "fadeIn" : 0,
9625 "fadeOut" : 0,
9626 "id" : "47F3B364-BE6C-473E-85C4-AD4A9DADF041",
9627 "kind" : "audio",
9628 "linkId" : "F572F9CE-FD52-495A-A477-9D6D62AC1086",
9629 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
9630 "muted" : true,
9631 "newShot" : false,
9632 "speed" : 1,
9633 "srcIn" : 2783.102569136626,
9634 "start" : 891.0508474576271,
9635 "track" : "v1"
9636 },
9637 {
9638 "duration" : 5.288135593220318,
9639 "fadeIn" : 0,
9640 "fadeOut" : 0,
9641 "id" : "E99A647D-A1D7-4937-883D-517D2AB47C45",
9642 "kind" : "audio",
9643 "linkId" : "F572F9CE-FD52-495A-A477-9D6D62AC1086",
9644 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
9645 "muted" : false,
9646 "newShot" : false,
9647 "speed" : 1,
9648 "srcIn" : 2783.1492454398567,
9649 "start" : 891.0508474576271,
9650 "track" : "v2"
9651 },
9652 {
9653 "duration" : 5.288135593220318,
9654 "fadeIn" : 0,
9655 "fadeOut" : 0,
9656 "id" : "68CBC79C-1130-4411-9396-AF8D0501E229",
9657 "kind" : "video",
9658 "linkId" : "F572F9CE-FD52-495A-A477-9D6D62AC1086",
9659 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
9660 "muted" : false,
9661 "newShot" : false,
9662 "speed" : 1,
9663 "srcIn" : 2783.0126383036286,
9664 "start" : 891.0508474576271,
9665 "track" : "v3"
9666 },
9667 {
9668 "duration" : 5.288135593220318,
9669 "fadeIn" : 0,
9670 "fadeOut" : 0,
9671 "id" : "B4AEE7C6-2A5A-4B03-B760-5FB897266EB9",
9672 "kind" : "video",
9673 "linkId" : "F572F9CE-FD52-495A-A477-9D6D62AC1086",
9674 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
9675 "muted" : false,
9676 "newShot" : false,
9677 "speed" : 1,
9678 "srcIn" : 2783.01263371963,
9679 "start" : 891.0508474576271,
9680 "track" : "v4"
9681 },
9682 {
9683 "duration" : 25.96610169491521,
9684 "fadeIn" : 0,
9685 "fadeOut" : 0,
9686 "id" : "3A767D55-B04B-4029-9717-1C032BD7E834",
9687 "kind" : "video",
9688 "linkId" : "B52B0E53-2FA8-47CD-B126-B2D828808856",
9689 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
9690 "muted" : false,
9691 "newShot" : false,
9692 "speed" : 1,
9693 "srcIn" : 2807.152542372882,
9694 "start" : 896.3389830508474,
9695 "track" : "v0"
9696 },
9697 {
9698 "duration" : 25.96610169491521,
9699 "fadeIn" : 0,
9700 "fadeOut" : 0,
9701 "id" : "CF8D639C-6FA9-4562-BF86-A28192C1D6C3",
9702 "kind" : "audio",
9703 "linkId" : "B52B0E53-2FA8-47CD-B126-B2D828808856",
9704 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
9705 "muted" : true,
9706 "newShot" : false,
9707 "speed" : 1,
9708 "srcIn" : 2807.068670831541,
9709 "start" : 896.3389830508474,
9710 "track" : "v1"
9711 },
9712 {
9713 "duration" : 25.96610169491521,
9714 "fadeIn" : 0,
9715 "fadeOut" : 0,
9716 "id" : "304BEE54-6B93-44B2-BF5C-F5E0F8864DBB",
9717 "kind" : "audio",
9718 "linkId" : "B52B0E53-2FA8-47CD-B126-B2D828808856",
9719 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
9720 "muted" : false,
9721 "newShot" : false,
9722 "speed" : 1,
9723 "srcIn" : 2807.115347134772,
9724 "start" : 896.3389830508474,
9725 "track" : "v2"
9726 },
9727 {
9728 "duration" : 25.96610169491521,
9729 "fadeIn" : 0,
9730 "fadeOut" : 0,
9731 "id" : "7DAAE3B6-0D4A-4029-9891-8A2D9DD41EB6",
9732 "kind" : "video",
9733 "linkId" : "B52B0E53-2FA8-47CD-B126-B2D828808856",
9734 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
9735 "muted" : false,
9736 "newShot" : false,
9737 "speed" : 1,
9738 "srcIn" : 2806.978739998544,
9739 "start" : 896.3389830508474,
9740 "track" : "v3"
9741 },
9742 {
9743 "duration" : 25.96610169491521,
9744 "fadeIn" : 0,
9745 "fadeOut" : 0,
9746 "id" : "A7EC127B-861B-436B-973A-566A7282AB56",
9747 "kind" : "video",
9748 "linkId" : "B52B0E53-2FA8-47CD-B126-B2D828808856",
9749 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
9750 "muted" : false,
9751 "newShot" : false,
9752 "speed" : 1,
9753 "srcIn" : 2806.978735414545,
9754 "start" : 896.3389830508474,
9755 "track" : "v4"
9756 },
9757 {
9758 "duration" : 1.9322033898305335,
9759 "fadeIn" : 0,
9760 "fadeOut" : 0,
9761 "id" : "B9DADEF6-8878-42FB-BD66-05228CB67D3F",
9762 "kind" : "video",
9763 "linkId" : "6C868D02-D8F5-470C-891B-B0D8A8F52B42",
9764 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
9765 "muted" : false,
9766 "newShot" : false,
9767 "speed" : 1,
9768 "srcIn" : 2833.1186440677975,
9769 "start" : 922.3050847457627,
9770 "track" : "v0"
9771 },
9772 {
9773 "duration" : 1.9322033898305335,
9774 "fadeIn" : 0,
9775 "fadeOut" : 0,
9776 "id" : "43CC1239-2AD4-4790-8627-80441483FA43",
9777 "kind" : "audio",
9778 "linkId" : "6C868D02-D8F5-470C-891B-B0D8A8F52B42",
9779 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
9780 "muted" : true,
9781 "newShot" : false,
9782 "speed" : 1,
9783 "srcIn" : 2833.0347725264564,
9784 "start" : 922.3050847457627,
9785 "track" : "v1"
9786 },
9787 {
9788 "duration" : 1.9322033898305335,
9789 "fadeIn" : 0,
9790 "fadeOut" : 0,
9791 "id" : "3E10D3AF-E56C-4B77-B15A-7A6A433E78B5",
9792 "kind" : "audio",
9793 "linkId" : "6C868D02-D8F5-470C-891B-B0D8A8F52B42",
9794 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
9795 "muted" : false,
9796 "newShot" : false,
9797 "speed" : 1,
9798 "srcIn" : 2833.0814488296874,
9799 "start" : 922.3050847457627,
9800 "track" : "v2"
9801 },
9802 {
9803 "duration" : 1.9322033898305335,
9804 "fadeIn" : 0,
9805 "fadeOut" : 0,
9806 "id" : "0AE9423C-B5CA-40D7-A25F-1D5950077D5C",
9807 "kind" : "video",
9808 "linkId" : "6C868D02-D8F5-470C-891B-B0D8A8F52B42",
9809 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
9810 "muted" : false,
9811 "newShot" : false,
9812 "speed" : 1,
9813 "srcIn" : 2832.9448416934592,
9814 "start" : 922.3050847457627,
9815 "track" : "v3"
9816 },
9817 {
9818 "duration" : 1.9322033898305335,
9819 "fadeIn" : 0,
9820 "fadeOut" : 0,
9821 "id" : "FA6C223A-7F5A-4150-A5F4-D2B4B445D9C0",
9822 "kind" : "video",
9823 "linkId" : "6C868D02-D8F5-470C-891B-B0D8A8F52B42",
9824 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
9825 "muted" : false,
9826 "newShot" : false,
9827 "speed" : 1,
9828 "srcIn" : 2832.9448371094604,
9829 "start" : 922.3050847457627,
9830 "track" : "v4"
9831 },
9832 {
9833 "duration" : 7.186440677966175,
9834 "fadeIn" : 0,
9835 "fadeOut" : 0,
9836 "id" : "934F79A8-575E-43F0-AD6C-F24F5199B7CB",
9837 "kind" : "video",
9838 "linkId" : "851CBD5D-70B1-429E-9236-7ECFEDAAA252",
9839 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
9840 "muted" : false,
9841 "newShot" : false,
9842 "speed" : 1,
9843 "srcIn" : 2838.7118644067805,
9844 "start" : 924.2372881355932,
9845 "track" : "v0"
9846 },
9847 {
9848 "duration" : 7.186440677966175,
9849 "fadeIn" : 0,
9850 "fadeOut" : 0,
9851 "id" : "A6FAE2FB-6564-42B5-ACC5-56029B3331BE",
9852 "kind" : "audio",
9853 "linkId" : "851CBD5D-70B1-429E-9236-7ECFEDAAA252",
9854 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
9855 "muted" : true,
9856 "newShot" : false,
9857 "speed" : 1,
9858 "srcIn" : 2838.6279928654394,
9859 "start" : 924.2372881355932,
9860 "track" : "v1"
9861 },
9862 {
9863 "duration" : 7.186440677966175,
9864 "fadeIn" : 0,
9865 "fadeOut" : 0,
9866 "id" : "EE147F25-D8F4-43EB-B6A4-DE05CB21BB12",
9867 "kind" : "audio",
9868 "linkId" : "851CBD5D-70B1-429E-9236-7ECFEDAAA252",
9869 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
9870 "muted" : false,
9871 "newShot" : false,
9872 "speed" : 1,
9873 "srcIn" : 2838.6746691686703,
9874 "start" : 924.2372881355932,
9875 "track" : "v2"
9876 },
9877 {
9878 "duration" : 7.186440677966175,
9879 "fadeIn" : 0,
9880 "fadeOut" : 0,
9881 "id" : "4890D002-161D-4D3E-A662-D441F2232FFF",
9882 "kind" : "video",
9883 "linkId" : "851CBD5D-70B1-429E-9236-7ECFEDAAA252",
9884 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
9885 "muted" : false,
9886 "newShot" : false,
9887 "speed" : 1,
9888 "srcIn" : 2838.538062032442,
9889 "start" : 924.2372881355932,
9890 "track" : "v3"
9891 },
9892 {
9893 "duration" : 7.186440677966175,
9894 "fadeIn" : 0,
9895 "fadeOut" : 0,
9896 "id" : "9BAE233F-FFA4-4044-A029-3B4EC16018C3",
9897 "kind" : "video",
9898 "linkId" : "851CBD5D-70B1-429E-9236-7ECFEDAAA252",
9899 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
9900 "muted" : false,
9901 "newShot" : false,
9902 "speed" : 1,
9903 "srcIn" : 2838.5380574484434,
9904 "start" : 924.2372881355932,
9905 "track" : "v4"
9906 },
9907 {
9908 "duration" : 3.830508474576277,
9909 "fadeIn" : 0,
9910 "fadeOut" : 0,
9911 "id" : "FB84EAA1-5B89-4489-89B4-D080098A1CD8",
9912 "kind" : "video",
9913 "linkId" : "6206B630-2FC1-4084-9937-5E08F99F118D",
9914 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
9915 "muted" : false,
9916 "newShot" : false,
9917 "speed" : 1,
9918 "srcIn" : 2890.5084745762715,
9919 "start" : 931.4237288135594,
9920 "track" : "v0"
9921 },
9922 {
9923 "duration" : 3.830508474576277,
9924 "fadeIn" : 0,
9925 "fadeOut" : 0,
9926 "id" : "EE6692DD-2585-4607-A46A-C6A25FB86B72",
9927 "kind" : "audio",
9928 "linkId" : "6206B630-2FC1-4084-9937-5E08F99F118D",
9929 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
9930 "muted" : true,
9931 "newShot" : false,
9932 "speed" : 1,
9933 "srcIn" : 2890.4246030349304,
9934 "start" : 931.4237288135594,
9935 "track" : "v1"
9936 },
9937 {
9938 "duration" : 3.830508474576277,
9939 "fadeIn" : 0,
9940 "fadeOut" : 0,
9941 "id" : "78A13F6F-DCA5-491A-8B52-0FC2409760AB",
9942 "kind" : "audio",
9943 "linkId" : "6206B630-2FC1-4084-9937-5E08F99F118D",
9944 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
9945 "muted" : false,
9946 "newShot" : false,
9947 "speed" : 1,
9948 "srcIn" : 2890.4712793381614,
9949 "start" : 931.4237288135594,
9950 "track" : "v2"
9951 },
9952 {
9953 "duration" : 3.830508474576277,
9954 "fadeIn" : 0,
9955 "fadeOut" : 0,
9956 "id" : "629569A0-6C4A-466A-AFA1-918C22E2E018",
9957 "kind" : "video",
9958 "linkId" : "6206B630-2FC1-4084-9937-5E08F99F118D",
9959 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
9960 "muted" : false,
9961 "newShot" : false,
9962 "speed" : 1,
9963 "srcIn" : 2890.3346722019332,
9964 "start" : 931.4237288135594,
9965 "track" : "v3"
9966 },
9967 {
9968 "duration" : 3.830508474576277,
9969 "fadeIn" : 0,
9970 "fadeOut" : 0,
9971 "id" : "55E9F328-E350-4B59-ACEA-6C4EF5E5A345",
9972 "kind" : "video",
9973 "linkId" : "6206B630-2FC1-4084-9937-5E08F99F118D",
9974 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
9975 "muted" : false,
9976 "newShot" : false,
9977 "speed" : 1,
9978 "srcIn" : 2890.3346676179344,
9979 "start" : 931.4237288135594,
9980 "track" : "v4"
9981 },
9982 {
9983 "duration" : 3.016949152542338,
9984 "fadeIn" : 0,
9985 "fadeOut" : 0,
9986 "id" : "61D97AD3-FA77-4D5E-85F2-0D8E088424D8",
9987 "kind" : "video",
9988 "linkId" : "03409808-1CF1-4E22-8CFC-D7F337CA20C0",
9989 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
9990 "muted" : false,
9991 "newShot" : false,
9992 "speed" : 1,
9993 "srcIn" : 2897.593220338983,
9994 "start" : 935.2542372881356,
9995 "track" : "v0"
9996 },
9997 {
9998 "duration" : 3.016949152542338,
9999 "fadeIn" : 0,
10000 "fadeOut" : 0,
10001 "id" : "A4B63EE7-142A-4568-B3A3-9BD1DFE06CE5",
10002 "kind" : "audio",
10003 "linkId" : "03409808-1CF1-4E22-8CFC-D7F337CA20C0",
10004 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
10005 "muted" : true,
10006 "newShot" : false,
10007 "speed" : 1,
10008 "srcIn" : 2897.509348797642,
10009 "start" : 935.2542372881356,
10010 "track" : "v1"
10011 },
10012 {
10013 "duration" : 3.016949152542338,
10014 "fadeIn" : 0,
10015 "fadeOut" : 0,
10016 "id" : "2195C3FF-C478-48A3-B480-A8D661ECF7EF",
10017 "kind" : "audio",
10018 "linkId" : "03409808-1CF1-4E22-8CFC-D7F337CA20C0",
10019 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
10020 "muted" : false,
10021 "newShot" : false,
10022 "speed" : 1,
10023 "srcIn" : 2897.556025100873,
10024 "start" : 935.2542372881356,
10025 "track" : "v2"
10026 },
10027 {
10028 "duration" : 3.016949152542338,
10029 "fadeIn" : 0,
10030 "fadeOut" : 0,
10031 "id" : "12215B20-BCE9-4ED2-BEC6-1EF6791F42B1",
10032 "kind" : "video",
10033 "linkId" : "03409808-1CF1-4E22-8CFC-D7F337CA20C0",
10034 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
10035 "muted" : false,
10036 "newShot" : false,
10037 "speed" : 1,
10038 "srcIn" : 2897.4194179646447,
10039 "start" : 935.2542372881356,
10040 "track" : "v3"
10041 },
10042 {
10043 "duration" : 3.016949152542338,
10044 "fadeIn" : 0,
10045 "fadeOut" : 0,
10046 "id" : "416FCED0-3BEE-4052-95C4-0D4395DF944A",
10047 "kind" : "video",
10048 "linkId" : "03409808-1CF1-4E22-8CFC-D7F337CA20C0",
10049 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
10050 "muted" : false,
10051 "newShot" : false,
10052 "speed" : 1,
10053 "srcIn" : 2897.419413380646,
10054 "start" : 935.2542372881356,
10055 "track" : "v4"
10056 },
10057 {
10058 "duration" : 0.8135593220338251,
10059 "fadeIn" : 0,
10060 "fadeOut" : 0,
10061 "id" : "3D695665-26AC-4ECE-B055-5ED89B2AEF2E",
10062 "kind" : "video",
10063 "linkId" : "D7000904-0663-4407-824B-32447EFF33F6",
10064 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
10065 "muted" : false,
10066 "newShot" : false,
10067 "speed" : 1,
10068 "srcIn" : 2904.5423728813553,
10069 "start" : 938.271186440678,
10070 "track" : "v0"
10071 },
10072 {
10073 "duration" : 0.8135593220338251,
10074 "fadeIn" : 0,
10075 "fadeOut" : 0,
10076 "id" : "1895810B-430E-4DE6-8E5B-653139F5FBEE",
10077 "kind" : "audio",
10078 "linkId" : "D7000904-0663-4407-824B-32447EFF33F6",
10079 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
10080 "muted" : true,
10081 "newShot" : false,
10082 "speed" : 1,
10083 "srcIn" : 2904.458501340014,
10084 "start" : 938.271186440678,
10085 "track" : "v1"
10086 },
10087 {
10088 "duration" : 0.8135593220338251,
10089 "fadeIn" : 0,
10090 "fadeOut" : 0,
10091 "id" : "0E102FE5-7926-4A40-9B94-CF405C37EB75",
10092 "kind" : "audio",
10093 "linkId" : "D7000904-0663-4407-824B-32447EFF33F6",
10094 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
10095 "muted" : false,
10096 "newShot" : false,
10097 "speed" : 1,
10098 "srcIn" : 2904.505177643245,
10099 "start" : 938.271186440678,
10100 "track" : "v2"
10101 },
10102 {
10103 "duration" : 0.8135593220338251,
10104 "fadeIn" : 0,
10105 "fadeOut" : 0,
10106 "id" : "222B3055-014A-4379-98CD-69EC43A35470",
10107 "kind" : "video",
10108 "linkId" : "D7000904-0663-4407-824B-32447EFF33F6",
10109 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
10110 "muted" : false,
10111 "newShot" : false,
10112 "speed" : 1,
10113 "srcIn" : 2904.368570507017,
10114 "start" : 938.271186440678,
10115 "track" : "v3"
10116 },
10117 {
10118 "duration" : 0.8135593220338251,
10119 "fadeIn" : 0,
10120 "fadeOut" : 0,
10121 "id" : "28CA102D-CDC8-46C5-988E-86F0EEF5EEDC",
10122 "kind" : "video",
10123 "linkId" : "D7000904-0663-4407-824B-32447EFF33F6",
10124 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
10125 "muted" : false,
10126 "newShot" : false,
10127 "speed" : 1,
10128 "srcIn" : 2904.368565923018,
10129 "start" : 938.271186440678,
10130 "track" : "v4"
10131 },
10132 {
10133 "duration" : 16.169491525423723,
10134 "fadeIn" : 0,
10135 "fadeOut" : 0,
10136 "id" : "1BBD2846-8898-43ED-B9EF-51ABB977D172",
10137 "kind" : "video",
10138 "linkId" : "441E27DB-C392-40BA-85B8-B3F421EF14E8",
10139 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
10140 "muted" : false,
10141 "newShot" : false,
10142 "speed" : 1,
10143 "srcIn" : 2905.3559322033893,
10144 "start" : 939.0847457627119,
10145 "track" : "v0"
10146 },
10147 {
10148 "duration" : 16.169491525423723,
10149 "fadeIn" : 0,
10150 "fadeOut" : 0,
10151 "id" : "0238A063-2A08-4FDB-920C-7372B455BCB8",
10152 "kind" : "audio",
10153 "linkId" : "441E27DB-C392-40BA-85B8-B3F421EF14E8",
10154 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
10155 "muted" : true,
10156 "newShot" : false,
10157 "speed" : 1,
10158 "srcIn" : 2905.2720606620483,
10159 "start" : 939.0847457627119,
10160 "track" : "v1"
10161 },
10162 {
10163 "duration" : 16.169491525423723,
10164 "fadeIn" : 0,
10165 "fadeOut" : 0,
10166 "id" : "CAF3113A-611A-4A6B-9A03-3539D6A0E4B1",
10167 "kind" : "audio",
10168 "linkId" : "441E27DB-C392-40BA-85B8-B3F421EF14E8",
10169 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
10170 "muted" : false,
10171 "newShot" : false,
10172 "speed" : 1,
10173 "srcIn" : 2905.318736965279,
10174 "start" : 939.0847457627119,
10175 "track" : "v2"
10176 },
10177 {
10178 "duration" : 16.169491525423723,
10179 "fadeIn" : 0,
10180 "fadeOut" : 0,
10181 "id" : "19424805-E973-46BC-BD0D-3547754230B4",
10182 "kind" : "video",
10183 "linkId" : "441E27DB-C392-40BA-85B8-B3F421EF14E8",
10184 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
10185 "muted" : false,
10186 "newShot" : false,
10187 "speed" : 1,
10188 "srcIn" : 2905.182129829051,
10189 "start" : 939.0847457627119,
10190 "track" : "v3"
10191 },
10192 {
10193 "duration" : 16.169491525423723,
10194 "fadeIn" : 0,
10195 "fadeOut" : 0,
10196 "id" : "D38B18BD-6C89-4507-A1EF-9B817B8E10CA",
10197 "kind" : "video",
10198 "linkId" : "441E27DB-C392-40BA-85B8-B3F421EF14E8",
10199 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
10200 "muted" : false,
10201 "newShot" : false,
10202 "speed" : 1,
10203 "srcIn" : 2905.1821252450522,
10204 "start" : 939.0847457627119,
10205 "track" : "v4"
10206 },
10207 {
10208 "duration" : 32.4406779661017,
10209 "fadeIn" : 0,
10210 "fadeOut" : 0,
10211 "id" : "92828BED-54D1-43DA-ADCC-A2784A7E60A8",
10212 "kind" : "video",
10213 "linkId" : "C90940D8-882F-4CBD-839A-3F097CE89ED2",
10214 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
10215 "muted" : false,
10216 "newShot" : false,
10217 "speed" : 1,
10218 "srcIn" : 2950.3389830508468,
10219 "start" : 955.2542372881356,
10220 "track" : "v0"
10221 },
10222 {
10223 "duration" : 32.4406779661017,
10224 "fadeIn" : 0,
10225 "fadeOut" : 0,
10226 "id" : "64CCF21E-AA71-4C5F-9CB3-EAE93F031C97",
10227 "kind" : "audio",
10228 "linkId" : "C90940D8-882F-4CBD-839A-3F097CE89ED2",
10229 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
10230 "muted" : true,
10231 "newShot" : false,
10232 "speed" : 1,
10233 "srcIn" : 2950.2551115095057,
10234 "start" : 955.2542372881356,
10235 "track" : "v1"
10236 },
10237 {
10238 "duration" : 32.4406779661017,
10239 "fadeIn" : 0,
10240 "fadeOut" : 0,
10241 "id" : "0EF2F1D8-B27D-425B-A77E-024D9E8ED834",
10242 "kind" : "audio",
10243 "linkId" : "C90940D8-882F-4CBD-839A-3F097CE89ED2",
10244 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
10245 "muted" : false,
10246 "newShot" : false,
10247 "speed" : 1,
10248 "srcIn" : 2950.3017878127366,
10249 "start" : 955.2542372881356,
10250 "track" : "v2"
10251 },
10252 {
10253 "duration" : 32.4406779661017,
10254 "fadeIn" : 0,
10255 "fadeOut" : 0,
10256 "id" : "7F966B3B-7499-46E3-A145-F4F6CB9899E5",
10257 "kind" : "video",
10258 "linkId" : "C90940D8-882F-4CBD-839A-3F097CE89ED2",
10259 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
10260 "muted" : false,
10261 "newShot" : false,
10262 "speed" : 1,
10263 "srcIn" : 2950.1651806765085,
10264 "start" : 955.2542372881356,
10265 "track" : "v3"
10266 },
10267 {
10268 "duration" : 32.4406779661017,
10269 "fadeIn" : 0,
10270 "fadeOut" : 0,
10271 "id" : "869C0AAC-694F-4A83-ACBD-BA02B2F1EBCA",
10272 "kind" : "video",
10273 "linkId" : "C90940D8-882F-4CBD-839A-3F097CE89ED2",
10274 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
10275 "muted" : false,
10276 "newShot" : false,
10277 "speed" : 1,
10278 "srcIn" : 2950.1651760925097,
10279 "start" : 955.2542372881356,
10280 "track" : "v4"
10281 },
10282 {
10283 "duration" : 2.5762711864406356,
10284 "fadeIn" : 0,
10285 "fadeOut" : 0,
10286 "id" : "25EDDE61-86BB-4F70-A496-23BF74AE9006",
10287 "kind" : "video",
10288 "linkId" : "9D7E5089-781D-46B7-83D2-8B95043855FA",
10289 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
10290 "muted" : false,
10291 "newShot" : false,
10292 "speed" : 1,
10293 "srcIn" : 2983.661016949152,
10294 "start" : 987.6949152542373,
10295 "track" : "v0"
10296 },
10297 {
10298 "duration" : 2.5762711864406356,
10299 "fadeIn" : 0,
10300 "fadeOut" : 0,
10301 "id" : "AAAAE9EC-5D0B-4491-B3AE-40D47947A105",
10302 "kind" : "audio",
10303 "linkId" : "9D7E5089-781D-46B7-83D2-8B95043855FA",
10304 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
10305 "muted" : true,
10306 "newShot" : false,
10307 "speed" : 1,
10308 "srcIn" : 2983.577145407811,
10309 "start" : 987.6949152542373,
10310 "track" : "v1"
10311 },
10312 {
10313 "duration" : 2.5762711864406356,
10314 "fadeIn" : 0,
10315 "fadeOut" : 0,
10316 "id" : "A75BBCB8-738D-4750-B235-F5EC59113946",
10317 "kind" : "audio",
10318 "linkId" : "9D7E5089-781D-46B7-83D2-8B95043855FA",
10319 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
10320 "muted" : false,
10321 "newShot" : false,
10322 "speed" : 1,
10323 "srcIn" : 2983.6238217110417,
10324 "start" : 987.6949152542373,
10325 "track" : "v2"
10326 },
10327 {
10328 "duration" : 2.5762711864406356,
10329 "fadeIn" : 0,
10330 "fadeOut" : 0,
10331 "id" : "71F23B1C-6343-4637-9DFC-DF10EEE27AD4",
10332 "kind" : "video",
10333 "linkId" : "9D7E5089-781D-46B7-83D2-8B95043855FA",
10334 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
10335 "muted" : false,
10336 "newShot" : false,
10337 "speed" : 1,
10338 "srcIn" : 2983.4872145748136,
10339 "start" : 987.6949152542373,
10340 "track" : "v3"
10341 },
10342 {
10343 "duration" : 2.5762711864406356,
10344 "fadeIn" : 0,
10345 "fadeOut" : 0,
10346 "id" : "BFF6C236-F0FD-45E2-B714-265E9B8F2102",
10347 "kind" : "video",
10348 "linkId" : "9D7E5089-781D-46B7-83D2-8B95043855FA",
10349 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
10350 "muted" : false,
10351 "newShot" : false,
10352 "speed" : 1,
10353 "srcIn" : 2983.487209990815,
10354 "start" : 987.6949152542373,
10355 "track" : "v4"
10356 },
10357 {
10358 "duration" : 14.101694915254257,
10359 "fadeIn" : 0,
10360 "fadeOut" : 0,
10361 "id" : "2B6D6836-A007-4343-9775-70DD28CB6EB9",
10362 "kind" : "video",
10363 "linkId" : "EFD99B70-8810-42EC-8F2B-5AA76217327E",
10364 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
10365 "muted" : false,
10366 "newShot" : false,
10367 "speed" : 1,
10368 "srcIn" : 2986.542372881355,
10369 "start" : 990.271186440678,
10370 "track" : "v0"
10371 },
10372 {
10373 "duration" : 14.101694915254257,
10374 "fadeIn" : 0,
10375 "fadeOut" : 0,
10376 "id" : "7E48882C-AD53-4651-90D8-9CBDFEF95F0C",
10377 "kind" : "audio",
10378 "linkId" : "EFD99B70-8810-42EC-8F2B-5AA76217327E",
10379 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
10380 "muted" : true,
10381 "newShot" : false,
10382 "speed" : 1,
10383 "srcIn" : 2986.4585013400138,
10384 "start" : 990.271186440678,
10385 "track" : "v1"
10386 },
10387 {
10388 "duration" : 14.101694915254257,
10389 "fadeIn" : 0,
10390 "fadeOut" : 0,
10391 "id" : "BA449FE2-AFB9-4E77-8799-8FE2429251A7",
10392 "kind" : "audio",
10393 "linkId" : "EFD99B70-8810-42EC-8F2B-5AA76217327E",
10394 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
10395 "muted" : false,
10396 "newShot" : false,
10397 "speed" : 1,
10398 "srcIn" : 2986.5051776432447,
10399 "start" : 990.271186440678,
10400 "track" : "v2"
10401 },
10402 {
10403 "duration" : 14.101694915254257,
10404 "fadeIn" : 0,
10405 "fadeOut" : 0,
10406 "id" : "A3C307CE-4B58-4DB3-B763-3452763F1277",
10407 "kind" : "video",
10408 "linkId" : "EFD99B70-8810-42EC-8F2B-5AA76217327E",
10409 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
10410 "muted" : false,
10411 "newShot" : false,
10412 "speed" : 1,
10413 "srcIn" : 2986.3685705070166,
10414 "start" : 990.271186440678,
10415 "track" : "v3"
10416 },
10417 {
10418 "duration" : 14.101694915254257,
10419 "fadeIn" : 0,
10420 "fadeOut" : 0,
10421 "id" : "322BFB74-8CE6-4C2F-950C-898A113AB445",
10422 "kind" : "video",
10423 "linkId" : "EFD99B70-8810-42EC-8F2B-5AA76217327E",
10424 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
10425 "muted" : false,
10426 "newShot" : false,
10427 "speed" : 1,
10428 "srcIn" : 2986.3685659230177,
10429 "start" : 990.271186440678,
10430 "track" : "v4"
10431 },
10432 {
10433 "duration" : 41.18644067796606,
10434 "fadeIn" : 0,
10435 "fadeOut" : 0,
10436 "id" : "1C0BB1B6-1862-4A8E-B78B-26DBECD3F37B",
10437 "kind" : "video",
10438 "linkId" : "9F7D047B-0DE6-4655-9D6E-5B8ADDD0267B",
10439 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
10440 "muted" : false,
10441 "newShot" : false,
10442 "speed" : 1,
10443 "srcIn" : 3016.4745762711855,
10444 "start" : 1004.3728813559322,
10445 "track" : "v0"
10446 },
10447 {
10448 "duration" : 41.18644067796606,
10449 "fadeIn" : 0,
10450 "fadeOut" : 0,
10451 "id" : "3DF07444-8948-4073-A7B7-DCD022163D06",
10452 "kind" : "audio",
10453 "linkId" : "9F7D047B-0DE6-4655-9D6E-5B8ADDD0267B",
10454 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
10455 "muted" : true,
10456 "newShot" : false,
10457 "speed" : 1,
10458 "srcIn" : 3016.3907047298444,
10459 "start" : 1004.3728813559322,
10460 "track" : "v1"
10461 },
10462 {
10463 "duration" : 41.18644067796606,
10464 "fadeIn" : 0,
10465 "fadeOut" : 0,
10466 "id" : "17518456-794B-46AE-8B57-11535384C6DD",
10467 "kind" : "audio",
10468 "linkId" : "9F7D047B-0DE6-4655-9D6E-5B8ADDD0267B",
10469 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
10470 "muted" : false,
10471 "newShot" : false,
10472 "speed" : 1,
10473 "srcIn" : 3016.4373810330753,
10474 "start" : 1004.3728813559322,
10475 "track" : "v2"
10476 },
10477 {
10478 "duration" : 41.18644067796606,
10479 "fadeIn" : 0,
10480 "fadeOut" : 0,
10481 "id" : "D83C6190-0569-4756-B034-B56F5EA95C6A",
10482 "kind" : "video",
10483 "linkId" : "9F7D047B-0DE6-4655-9D6E-5B8ADDD0267B",
10484 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
10485 "muted" : false,
10486 "newShot" : false,
10487 "speed" : 1,
10488 "srcIn" : 3016.300773896847,
10489 "start" : 1004.3728813559322,
10490 "track" : "v3"
10491 },
10492 {
10493 "duration" : 41.18644067796606,
10494 "fadeIn" : 0,
10495 "fadeOut" : 0,
10496 "id" : "04909166-A121-49EA-B441-2498FB853E8E",
10497 "kind" : "video",
10498 "linkId" : "9F7D047B-0DE6-4655-9D6E-5B8ADDD0267B",
10499 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
10500 "muted" : false,
10501 "newShot" : false,
10502 "speed" : 1,
10503 "srcIn" : 3016.3007693128484,
10504 "start" : 1004.3728813559322,
10505 "track" : "v4"
10506 },
10507 {
10508 "duration" : 1.661016949152554,
10509 "fadeIn" : 0,
10510 "fadeOut" : 0,
10511 "id" : "AB7E6E67-21DC-473D-A4F5-32C751A46EE1",
10512 "kind" : "video",
10513 "linkId" : "0B407464-C16F-4EE0-8709-4530856B5481",
10514 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
10515 "muted" : false,
10516 "newShot" : false,
10517 "speed" : 1,
10518 "srcIn" : 3065.593220338982,
10519 "start" : 1045.5593220338983,
10520 "track" : "v0"
10521 },
10522 {
10523 "duration" : 1.661016949152554,
10524 "fadeIn" : 0,
10525 "fadeOut" : 0,
10526 "id" : "3945B5C6-C770-47E5-BF52-BC27D9ABFAE0",
10527 "kind" : "audio",
10528 "linkId" : "0B407464-C16F-4EE0-8709-4530856B5481",
10529 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
10530 "muted" : true,
10531 "newShot" : false,
10532 "speed" : 1,
10533 "srcIn" : 3065.509348797641,
10534 "start" : 1045.5593220338983,
10535 "track" : "v1"
10536 },
10537 {
10538 "duration" : 1.661016949152554,
10539 "fadeIn" : 0,
10540 "fadeOut" : 0,
10541 "id" : "D21DCFAE-885B-46AB-BBCD-46F5F68A4841",
10542 "kind" : "audio",
10543 "linkId" : "0B407464-C16F-4EE0-8709-4530856B5481",
10544 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
10545 "muted" : false,
10546 "newShot" : false,
10547 "speed" : 1,
10548 "srcIn" : 3065.556025100872,
10549 "start" : 1045.5593220338983,
10550 "track" : "v2"
10551 },
10552 {
10553 "duration" : 1.661016949152554,
10554 "fadeIn" : 0,
10555 "fadeOut" : 0,
10556 "id" : "09CE071F-2BC1-44E9-80B4-A3B494754324",
10557 "kind" : "video",
10558 "linkId" : "0B407464-C16F-4EE0-8709-4530856B5481",
10559 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
10560 "muted" : false,
10561 "newShot" : false,
10562 "speed" : 1,
10563 "srcIn" : 3065.419417964644,
10564 "start" : 1045.5593220338983,
10565 "track" : "v3"
10566 },
10567 {
10568 "duration" : 1.661016949152554,
10569 "fadeIn" : 0,
10570 "fadeOut" : 0,
10571 "id" : "E59CB212-BAFB-4792-9E87-27750E214AFF",
10572 "kind" : "video",
10573 "linkId" : "0B407464-C16F-4EE0-8709-4530856B5481",
10574 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
10575 "muted" : false,
10576 "newShot" : false,
10577 "speed" : 1,
10578 "srcIn" : 3065.419413380645,
10579 "start" : 1045.5593220338983,
10580 "track" : "v4"
10581 },
10582 {
10583 "duration" : 4.745762711864472,
10584 "fadeIn" : 0,
10585 "fadeOut" : 0,
10586 "id" : "E92C9D2B-F806-42DF-BBBD-6444CED9CFA0",
10587 "kind" : "video",
10588 "linkId" : "BBA33081-9695-4391-B23B-D376880D264E",
10589 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
10590 "muted" : false,
10591 "newShot" : false,
10592 "speed" : 1,
10593 "srcIn" : 3070.203389830507,
10594 "start" : 1047.2203389830509,
10595 "track" : "v0"
10596 },
10597 {
10598 "duration" : 4.745762711864472,
10599 "fadeIn" : 0,
10600 "fadeOut" : 0,
10601 "id" : "C13910BD-C1D5-42AA-B25A-F4AFA7E0807E",
10602 "kind" : "audio",
10603 "linkId" : "BBA33081-9695-4391-B23B-D376880D264E",
10604 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
10605 "muted" : true,
10606 "newShot" : false,
10607 "speed" : 1,
10608 "srcIn" : 3070.119518289166,
10609 "start" : 1047.2203389830509,
10610 "track" : "v1"
10611 },
10612 {
10613 "duration" : 4.745762711864472,
10614 "fadeIn" : 0,
10615 "fadeOut" : 0,
10616 "id" : "6A6D3754-91FF-45E9-9A9C-EC342D95E136",
10617 "kind" : "audio",
10618 "linkId" : "BBA33081-9695-4391-B23B-D376880D264E",
10619 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
10620 "muted" : false,
10621 "newShot" : false,
10622 "speed" : 1,
10623 "srcIn" : 3070.166194592397,
10624 "start" : 1047.2203389830509,
10625 "track" : "v2"
10626 },
10627 {
10628 "duration" : 4.745762711864472,
10629 "fadeIn" : 0,
10630 "fadeOut" : 0,
10631 "id" : "122D9228-98AA-4877-A0C4-2DFD8CAE845B",
10632 "kind" : "video",
10633 "linkId" : "BBA33081-9695-4391-B23B-D376880D264E",
10634 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
10635 "muted" : false,
10636 "newShot" : false,
10637 "speed" : 1,
10638 "srcIn" : 3070.029587456169,
10639 "start" : 1047.2203389830509,
10640 "track" : "v3"
10641 },
10642 {
10643 "duration" : 4.745762711864472,
10644 "fadeIn" : 0,
10645 "fadeOut" : 0,
10646 "id" : "9124C8F4-A9B8-40C9-A87D-03794D9BFD5B",
10647 "kind" : "video",
10648 "linkId" : "BBA33081-9695-4391-B23B-D376880D264E",
10649 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
10650 "muted" : false,
10651 "newShot" : false,
10652 "speed" : 1,
10653 "srcIn" : 3070.02958287217,
10654 "start" : 1047.2203389830509,
10655 "track" : "v4"
10656 },
10657 {
10658 "duration" : 11.93220338983042,
10659 "fadeIn" : 0,
10660 "fadeOut" : 0,
10661 "id" : "28D0F5F8-073A-4EB4-ABCD-C489AF0CA087",
10662 "kind" : "video",
10663 "linkId" : "318B4FD4-E1D7-41FD-B760-CA9E5DAB9B1F",
10664 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
10665 "muted" : false,
10666 "newShot" : false,
10667 "speed" : 1,
10668 "srcIn" : 3082.271186440677,
10669 "start" : 1051.9661016949153,
10670 "track" : "v0"
10671 },
10672 {
10673 "duration" : 11.93220338983042,
10674 "fadeIn" : 0,
10675 "fadeOut" : 0,
10676 "id" : "2B967FFC-3967-43AC-B348-992E7603AE2C",
10677 "kind" : "audio",
10678 "linkId" : "318B4FD4-E1D7-41FD-B760-CA9E5DAB9B1F",
10679 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
10680 "muted" : true,
10681 "newShot" : false,
10682 "speed" : 1,
10683 "srcIn" : 3082.187314899336,
10684 "start" : 1051.9661016949153,
10685 "track" : "v1"
10686 },
10687 {
10688 "duration" : 11.93220338983042,
10689 "fadeIn" : 0,
10690 "fadeOut" : 0,
10691 "id" : "5DCE6786-7E43-4D1A-9415-C2E364A3C1E5",
10692 "kind" : "audio",
10693 "linkId" : "318B4FD4-E1D7-41FD-B760-CA9E5DAB9B1F",
10694 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
10695 "muted" : false,
10696 "newShot" : false,
10697 "speed" : 1,
10698 "srcIn" : 3082.233991202567,
10699 "start" : 1051.9661016949153,
10700 "track" : "v2"
10701 },
10702 {
10703 "duration" : 11.93220338983042,
10704 "fadeIn" : 0,
10705 "fadeOut" : 0,
10706 "id" : "CCB5A926-E4B6-4AA9-9EF3-2E8A78E7B2F2",
10707 "kind" : "video",
10708 "linkId" : "318B4FD4-E1D7-41FD-B760-CA9E5DAB9B1F",
10709 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
10710 "muted" : false,
10711 "newShot" : false,
10712 "speed" : 1,
10713 "srcIn" : 3082.0973840663387,
10714 "start" : 1051.9661016949153,
10715 "track" : "v3"
10716 },
10717 {
10718 "duration" : 11.93220338983042,
10719 "fadeIn" : 0,
10720 "fadeOut" : 0,
10721 "id" : "296DECA8-17FA-4732-85F9-E7E62AB4390E",
10722 "kind" : "video",
10723 "linkId" : "318B4FD4-E1D7-41FD-B760-CA9E5DAB9B1F",
10724 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
10725 "muted" : false,
10726 "newShot" : false,
10727 "speed" : 1,
10728 "srcIn" : 3082.09737948234,
10729 "start" : 1051.9661016949153,
10730 "track" : "v4"
10731 },
10732 {
10733 "duration" : 2.7118644067795685,
10734 "fadeIn" : 0,
10735 "fadeOut" : 0,
10736 "id" : "F118809D-1E6F-4E06-9DD8-59627D7FD31E",
10737 "kind" : "video",
10738 "linkId" : "D27F02ED-F905-4600-B733-AF4B73B5174F",
10739 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
10740 "muted" : false,
10741 "newShot" : false,
10742 "speed" : 1,
10743 "srcIn" : 3097.559322033897,
10744 "start" : 1063.8983050847457,
10745 "track" : "v0"
10746 },
10747 {
10748 "duration" : 2.7118644067795685,
10749 "fadeIn" : 0,
10750 "fadeOut" : 0,
10751 "id" : "4A2040D0-20BE-429F-AD73-5B3ED24C0A6D",
10752 "kind" : "audio",
10753 "linkId" : "D27F02ED-F905-4600-B733-AF4B73B5174F",
10754 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
10755 "muted" : true,
10756 "newShot" : false,
10757 "speed" : 1,
10758 "srcIn" : 3097.475450492556,
10759 "start" : 1063.8983050847457,
10760 "track" : "v1"
10761 },
10762 {
10763 "duration" : 2.7118644067795685,
10764 "fadeIn" : 0,
10765 "fadeOut" : 0,
10766 "id" : "3D418272-0D62-4126-A9C7-71E7D7FC255D",
10767 "kind" : "audio",
10768 "linkId" : "D27F02ED-F905-4600-B733-AF4B73B5174F",
10769 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
10770 "muted" : false,
10771 "newShot" : false,
10772 "speed" : 1,
10773 "srcIn" : 3097.522126795787,
10774 "start" : 1063.8983050847457,
10775 "track" : "v2"
10776 },
10777 {
10778 "duration" : 2.7118644067795685,
10779 "fadeIn" : 0,
10780 "fadeOut" : 0,
10781 "id" : "B50F2156-B9AC-4F52-B362-40B0B0D732A4",
10782 "kind" : "video",
10783 "linkId" : "D27F02ED-F905-4600-B733-AF4B73B5174F",
10784 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
10785 "muted" : false,
10786 "newShot" : false,
10787 "speed" : 1,
10788 "srcIn" : 3097.3855196595587,
10789 "start" : 1063.8983050847457,
10790 "track" : "v3"
10791 },
10792 {
10793 "duration" : 2.7118644067795685,
10794 "fadeIn" : 0,
10795 "fadeOut" : 0,
10796 "id" : "8C35ED3F-0857-4157-9DDF-8937A65375E5",
10797 "kind" : "video",
10798 "linkId" : "D27F02ED-F905-4600-B733-AF4B73B5174F",
10799 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
10800 "muted" : false,
10801 "newShot" : false,
10802 "speed" : 1,
10803 "srcIn" : 3097.38551507556,
10804 "start" : 1063.8983050847457,
10805 "track" : "v4"
10806 },
10807 {
10808 "duration" : 29.864406779661067,
10809 "fadeIn" : 0,
10810 "fadeOut" : 0,
10811 "id" : "5F3F6F18-5791-47A8-BCA4-A9790558A314",
10812 "kind" : "video",
10813 "linkId" : "0F08CC53-E7F9-4A91-9D1E-F73D91D3ECB9",
10814 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
10815 "muted" : false,
10816 "newShot" : false,
10817 "speed" : 1,
10818 "srcIn" : 3101.3559322033884,
10819 "start" : 1066.6101694915253,
10820 "track" : "v0"
10821 },
10822 {
10823 "duration" : 29.864406779661067,
10824 "fadeIn" : 0,
10825 "fadeOut" : 0,
10826 "id" : "CF1D8F7D-26D5-4CD2-8D99-2370B29915C0",
10827 "kind" : "audio",
10828 "linkId" : "0F08CC53-E7F9-4A91-9D1E-F73D91D3ECB9",
10829 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
10830 "muted" : true,
10831 "newShot" : false,
10832 "speed" : 1,
10833 "srcIn" : 3101.2720606620474,
10834 "start" : 1066.6101694915253,
10835 "track" : "v1"
10836 },
10837 {
10838 "duration" : 29.864406779661067,
10839 "fadeIn" : 0,
10840 "fadeOut" : 0,
10841 "id" : "A1160BCB-0740-4FC5-BEA2-A90D6030BFA6",
10842 "kind" : "audio",
10843 "linkId" : "0F08CC53-E7F9-4A91-9D1E-F73D91D3ECB9",
10844 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
10845 "muted" : false,
10846 "newShot" : false,
10847 "speed" : 1,
10848 "srcIn" : 3101.3187369652783,
10849 "start" : 1066.6101694915253,
10850 "track" : "v2"
10851 },
10852 {
10853 "duration" : 29.864406779661067,
10854 "fadeIn" : 0,
10855 "fadeOut" : 0,
10856 "id" : "8E323D18-4C84-4AFA-B90D-264EE10DBF94",
10857 "kind" : "video",
10858 "linkId" : "0F08CC53-E7F9-4A91-9D1E-F73D91D3ECB9",
10859 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
10860 "muted" : false,
10861 "newShot" : false,
10862 "speed" : 1,
10863 "srcIn" : 3101.18212982905,
10864 "start" : 1066.6101694915253,
10865 "track" : "v3"
10866 },
10867 {
10868 "duration" : 29.864406779661067,
10869 "fadeIn" : 0,
10870 "fadeOut" : 0,
10871 "id" : "A1E18A2B-C2ED-4F4F-B090-2F04AB1EAB86",
10872 "kind" : "video",
10873 "linkId" : "0F08CC53-E7F9-4A91-9D1E-F73D91D3ECB9",
10874 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
10875 "muted" : false,
10876 "newShot" : false,
10877 "speed" : 1,
10878 "srcIn" : 3101.1821252450513,
10879 "start" : 1066.6101694915253,
10880 "track" : "v4"
10881 },
10882 {
10883 "duration" : 3046.474576440677,
10884 "fadeIn" : 0,
10885 "fadeOut" : 0,
10886 "id" : "77231674-DFD4-469D-8BD3-B0384766ED44",
10887 "kind" : "video",
10888 "linkId" : "06145E01-F314-4D25-9F5A-C1D23472B857",
10889 "mediaId" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
10890 "muted" : false,
10891 "newShot" : false,
10892 "speed" : 1,
10893 "srcIn" : 3175.7288135593208,
10894 "start" : 1096.4745762711864,
10895 "track" : "v0"
10896 },
10897 {
10898 "duration" : 3046.477724982019,
10899 "fadeIn" : 0,
10900 "fadeOut" : 0,
10901 "id" : "46D41482-2761-4F49-8714-E482340928D2",
10902 "kind" : "audio",
10903 "linkId" : "06145E01-F314-4D25-9F5A-C1D23472B857",
10904 "mediaId" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
10905 "muted" : true,
10906 "newShot" : false,
10907 "speed" : 1,
10908 "srcIn" : 3175.6449420179797,
10909 "start" : 1096.4745762711864,
10910 "track" : "v1"
10911 },
10912 {
10913 "duration" : 3046.516381678787,
10914 "fadeIn" : 0,
10915 "fadeOut" : 0,
10916 "id" : "6655D05A-2F89-4E98-A2C0-475D1908D6DB",
10917 "kind" : "audio",
10918 "linkId" : "06145E01-F314-4D25-9F5A-C1D23472B857",
10919 "mediaId" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
10920 "muted" : false,
10921 "newShot" : false,
10922 "speed" : 1,
10923 "srcIn" : 3175.6916183212106,
10924 "start" : 1096.4745762711864,
10925 "track" : "v2"
10926 },
10927 {
10928 "duration" : 3046.5116558150157,
10929 "fadeIn" : 0,
10930 "fadeOut" : 0,
10931 "id" : "0BA9AB3D-B156-466B-A477-DD6CBCC6D5D0",
10932 "kind" : "video",
10933 "linkId" : "06145E01-F314-4D25-9F5A-C1D23472B857",
10934 "mediaId" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
10935 "muted" : false,
10936 "newShot" : false,
10937 "speed" : 1,
10938 "srcIn" : 3175.5550111849825,
10939 "start" : 1096.4745762711864,
10940 "track" : "v3"
10941 },
10942 {
10943 "duration" : 3046.5116603990145,
10944 "fadeIn" : 0,
10945 "fadeOut" : 0,
10946 "id" : "FA5C83A2-F1FC-4E94-B0A3-D12DF73B9D65",
10947 "kind" : "video",
10948 "linkId" : "06145E01-F314-4D25-9F5A-C1D23472B857",
10949 "mediaId" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
10950 "muted" : false,
10951 "newShot" : false,
10952 "speed" : 1,
10953 "srcIn" : 3175.5550066009837,
10954 "start" : 1096.4745762711864,
10955 "track" : "v4"
10956 }
10957 ],
10958 "fps" : 29.5,
10959 "markers" : [
10960
10961 ],
10962 "media" : [
10963 {
10964 "cacheKey" : "cb2b0cb1418eb04f",
10965 "duration" : 812.745763,
10966 "fps" : 29.5,
10967 "hasAudio" : false,
10968 "height" : 720,
10969 "id" : "26CBEF77-6850-4D82-A5BD-A6A37587C040",
10970 "isAudio" : false,
10971 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_13.49\/cam.mov",
10972 "width" : 1280
10973 },
10974 {
10975 "cacheKey" : "49108ecb3168df5a",
10976 "duration" : 812.885333,
10977 "fps" : 30,
10978 "hasAudio" : true,
10979 "height" : 0,
10980 "id" : "F676BA7D-A0E0-4603-AE13-B853D2F9F2BB",
10981 "isAudio" : true,
10982 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_13.49\/desktop.m4a",
10983 "width" : 0
10984 },
10985 {
10986 "cacheKey" : "9f6621781616504d",
10987 "duration" : 813.269333,
10988 "fps" : 30,
10989 "hasAudio" : true,
10990 "height" : 0,
10991 "id" : "6FFA3786-8E51-4E69-A15D-D6A991C0CCAD",
10992 "isAudio" : true,
10993 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_13.49\/mic.m4a",
10994 "width" : 0
10995 },
10996 {
10997 "cacheKey" : "fbe623fb3a0f34ba",
10998 "duration" : 812.833333,
10999 "fps" : 30,
11000 "hasAudio" : false,
11001 "height" : 2160,
11002 "id" : "609E978E-5994-4757-A6C7-EAAE72818A49",
11003 "isAudio" : false,
11004 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_13.49\/screen-1.mov",
11005 "width" : 3840
11006 },
11007 {
11008 "cacheKey" : "1be7aaff98d7b0ff",
11009 "duration" : 812.833333,
11010 "fps" : 30,
11011 "hasAudio" : false,
11012 "height" : 2160,
11013 "id" : "C0E05C14-2D32-4A55-9041-7A752A97F6B1",
11014 "isAudio" : false,
11015 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_13.49\/screen-2.mov",
11016 "width" : 3840
11017 },
11018 {
11019 "cacheKey" : "bc4651748a6b1673",
11020 "duration" : 6222.20339,
11021 "fps" : 29.5,
11022 "hasAudio" : false,
11023 "height" : 720,
11024 "id" : "40BBB768-793D-44DA-BBC9-B22F6591159D",
11025 "isAudio" : false,
11026 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_14.03\/cam.mov",
11027 "width" : 1280
11028 },
11029 {
11030 "cacheKey" : "27d4dedeccdacecc",
11031 "duration" : 6222.122667,
11032 "fps" : 30,
11033 "hasAudio" : true,
11034 "height" : 0,
11035 "id" : "55C13A17-0423-4BC0-93A6-3FEFD02E7FC6",
11036 "isAudio" : true,
11037 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_14.03\/desktop.m4a",
11038 "width" : 0
11039 },
11040 {
11041 "cacheKey" : "103551fc583e9882",
11042 "duration" : 6222.208,
11043 "fps" : 30,
11044 "hasAudio" : true,
11045 "height" : 0,
11046 "id" : "501034F8-09AC-44B1-8826-FCE9B36C68F9",
11047 "isAudio" : true,
11048 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_14.03\/mic.m4a",
11049 "width" : 0
11050 },
11051 {
11052 "cacheKey" : "5bfa7a15f159568d",
11053 "duration" : 6222.066667,
11054 "fps" : 30,
11055 "hasAudio" : false,
11056 "height" : 2160,
11057 "id" : "D866E8BE-A5B1-4B51-B465-D3F753701CCE",
11058 "isAudio" : false,
11059 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_14.03\/screen-1.mov",
11060 "width" : 3840
11061 },
11062 {
11063 "cacheKey" : "8cea14167c6e9778",
11064 "duration" : 6222.066667,
11065 "fps" : 30,
11066 "hasAudio" : false,
11067 "height" : 2160,
11068 "id" : "92E85F7F-CA8B-473D-82F6-C7A7418875B1",
11069 "isAudio" : false,
11070 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_14.03\/screen-2.mov",
11071 "width" : 3840
11072 },
11073 {
11074 "cacheKey" : "b966487289885ec1",
11075 "duration" : 15112.237288,
11076 "fps" : 29.5,
11077 "hasAudio" : false,
11078 "height" : 720,
11079 "id" : "CC300C55-7090-4A6E-81FA-55CB6644FAC9",
11080 "isAudio" : false,
11081 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_16.36\/cam.mov",
11082 "width" : 1280
11083 },
11084 {
11085 "cacheKey" : "9044d1d53bcf480f",
11086 "duration" : 15112.106667,
11087 "fps" : 30,
11088 "hasAudio" : true,
11089 "height" : 0,
11090 "id" : "2613683E-12E6-402E-BF4D-6D9ACD7C1491",
11091 "isAudio" : true,
11092 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_16.36\/desktop.m4a",
11093 "width" : 0
11094 },
11095 {
11096 "cacheKey" : "36fc42c715b64632",
11097 "duration" : 15112.213333,
11098 "fps" : 30,
11099 "hasAudio" : true,
11100 "height" : 0,
11101 "id" : "7889E9EC-F67A-491B-81C0-0CF8338965A3",
11102 "isAudio" : true,
11103 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_16.36\/mic.m4a",
11104 "width" : 0
11105 },
11106 {
11107 "cacheKey" : "0308132cb01a092a",
11108 "duration" : 15112.066667,
11109 "fps" : 30,
11110 "hasAudio" : false,
11111 "height" : 2160,
11112 "id" : "BC4E74C5-4C45-4633-B31A-3CAA58D4B2BF",
11113 "isAudio" : false,
11114 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_16.36\/screen-1.mov",
11115 "width" : 3840
11116 },
11117 {
11118 "cacheKey" : "270afa9f16a6fb41",
11119 "duration" : 15111.366667,
11120 "fps" : 30,
11121 "hasAudio" : false,
11122 "height" : 2160,
11123 "id" : "98882264-A2ED-477B-A7B8-B63E54EDEACE",
11124 "isAudio" : false,
11125 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_16.36\/screen-2.mov",
11126 "width" : 3840
11127 },
11128 {
11129 "cacheKey" : "02a6eb97d64d4630",
11130 "duration" : 10045.254237,
11131 "fps" : 29.5,
11132 "hasAudio" : false,
11133 "height" : 720,
11134 "id" : "2D5EED57-87EB-44B4-9623-2CF48314C5F9",
11135 "isAudio" : false,
11136 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_22.54\/cam.mov",
11137 "width" : 1280
11138 },
11139 {
11140 "cacheKey" : "cc655edd968d5060",
11141 "duration" : 10045.162667,
11142 "fps" : 30,
11143 "hasAudio" : true,
11144 "height" : 0,
11145 "id" : "0ECB1F4F-9065-432F-B0B1-7F47BD31FDCC",
11146 "isAudio" : true,
11147 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_22.54\/desktop.m4a",
11148 "width" : 0
11149 },
11150 {
11151 "cacheKey" : "4a0d44fc07d2db63",
11152 "duration" : 10045.248,
11153 "fps" : 30,
11154 "hasAudio" : true,
11155 "height" : 0,
11156 "id" : "02A5DFB3-9348-4102-BD13-F1856B431A70",
11157 "isAudio" : true,
11158 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_22.54\/mic.m4a",
11159 "width" : 0
11160 },
11161 {
11162 "cacheKey" : "edc26bd451155492",
11163 "duration" : 10045.133333,
11164 "fps" : 30,
11165 "hasAudio" : false,
11166 "height" : 2160,
11167 "id" : "9CA51332-5865-4C3D-9972-E781BDE75ECD",
11168 "isAudio" : false,
11169 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_22.54\/screen-1.mov",
11170 "width" : 3840
11171 },
11172 {
11173 "cacheKey" : "3c45ae09f47beeae",
11174 "duration" : 10045.166667,
11175 "fps" : 30,
11176 "hasAudio" : false,
11177 "height" : 2160,
11178 "id" : "0DEB0D7E-0C1E-49DA-9911-7773FB2158DE",
11179 "isAudio" : false,
11180 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-03_22.54\/screen-2.mov",
11181 "width" : 3840
11182 },
11183 {
11184 "cacheKey" : "81f385d21caafc19",
11185 "duration" : 11597.966102,
11186 "fps" : 29.5,
11187 "hasAudio" : false,
11188 "height" : 720,
11189 "id" : "F06DF483-F96F-4DD1-8602-53609FA888F1",
11190 "isAudio" : false,
11191 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-04_17.30\/cam.mov",
11192 "width" : 1280
11193 },
11194 {
11195 "cacheKey" : "88621a686d385fe8",
11196 "duration" : 11598.101333,
11197 "fps" : 30,
11198 "hasAudio" : true,
11199 "height" : 0,
11200 "id" : "59213540-870A-4795-BD44-B3AFBC9F14E3",
11201 "isAudio" : true,
11202 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-04_17.30\/desktop.m4a",
11203 "width" : 0
11204 },
11205 {
11206 "cacheKey" : "3ee60110dd18110c",
11207 "duration" : 11598.464,
11208 "fps" : 30,
11209 "hasAudio" : true,
11210 "height" : 0,
11211 "id" : "F4DB0AF3-2C74-4501-A58A-A519C046E3D6",
11212 "isAudio" : true,
11213 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-04_17.30\/mic.m4a",
11214 "width" : 0
11215 },
11216 {
11217 "cacheKey" : "290f82433dd61a4c",
11218 "duration" : 11598.033333,
11219 "fps" : 30,
11220 "hasAudio" : false,
11221 "height" : 2160,
11222 "id" : "9F95D76B-EAB2-49AA-897A-C3C9A265E0CC",
11223 "isAudio" : false,
11224 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-04_17.30\/screen-1.mov",
11225 "width" : 3840
11226 },
11227 {
11228 "cacheKey" : "12165002971af6ff",
11229 "duration" : 11598,
11230 "fps" : 30,
11231 "hasAudio" : false,
11232 "height" : 2160,
11233 "id" : "CC497E03-E3E7-4283-A96C-F076D9F154A1",
11234 "isAudio" : false,
11235 "path" : "\/Volumes\/clover\/Archive\/2026\/Journal\/2026-07-04_17.30\/screen-2.mov",
11236 "width" : 3840
11237 }
11238 ],
11239 "preferredTakes" : [
11240
11241 ],
11242 "tracks" : [
11243 {
11244 "hue" : 0
11245 },
11246 {
11247 "hue" : 0.6180339887498949
11248 },
11249 {
11250 "hue" : 0.2360679774997898
11251 },
11252 {
11253 "hue" : 0.8541019662496847
11254 },
11255 {
11256 "hue" : 0.4721359549995796
11257 }
11258 ]
11259 },
11260 "view" : {
11261 "focusedTracks" : [
11262
11263 ],
11264 "fusionFocus" : false,
11265 "fusionHidden" : false,
11266 "hiddenTracks" : [
11267
11268 ],
11269 "laneScale" : 1.5562744140625,
11270 "previewsOnLeft" : false,
11271 "priorityPane" : "v4",
11272 "showFilmstrips" : true,
11273 "snapping" : true,
11274 "trackHeights" : [
11275 {
11276 "factor" : 0.99639892578125,
11277 "track" : "v0"
11278 },
11279 {
11280 "factor" : 1,
11281 "track" : "v4"
11282 }
11283 ]
11284 }
11285}
\ No newline at end of file
sequencer/Sources/Sequencer/AppDelegate.swift+7
......@@ -52,6 +52,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
5252 /// (Fusion-feeding) workflow.
5353 func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { true }
5454
55 /// In-flight ffmpeg encodes must die with the app: orphans hold shared
56 /// VideoToolbox decode sessions, and a few accumulated ones make every
57 /// AVPlayer in the NEXT instance render black (see MediaPipeline).
58 func applicationWillTerminate(_ notification: Notification) {
59 MediaPipeline.terminateChildren()
60 }
61
5562 /// On launch, reopen the most recent project instead of a blank untitled
5663 /// one; fall back to a fresh untitled document when there's no history.
5764 func applicationOpenUntitledFile(_ sender: NSApplication) -> Bool {
sequencer/Sources/Sequencer/Cachetest.swift created+132
......@@ -0,0 +1,132 @@
1import Foundation
2
3/// Headless cache-budget check: `sequencer --cachetest <file.sq> [capGB]`.
4///
5/// Loads the real project into the headless context, jumps the playhead to
6/// several spots spread across the timeline, and lets demand builds +
7/// evictions run at each stop while continuously asserting the invariant
8/// that makes the cap a hard ceiling:
9///
10/// ledger + reserved ≤ cap (checked every second)
11/// bytes on disk ≤ cap + slack (checked every few seconds)
12///
13/// At each stop it also waits for the chunk under the playhead to become
14/// covered — proving the system optimizes around where the user is even when
15/// the whole project can't fit (the "Deltarune problem").
16func runCacheTest(path: String, capGB: Int) {
17 func spin(_ seconds: Double) {
18 RunLoop.main.run(until: Date().addingTimeInterval(seconds))
19 }
20
21 print("== Sequencer cachetest ==")
22 if capGB > 0 { UserDefaults.standard.set(capGB, forKey: "maxCacheGB") }
23 let pipeline = MediaPipeline.shared
24 let cap = pipeline.maxCacheBytes
25 print("cache: \(pipeline.cacheRoot.path)")
26 print(String(format: "cap: %.1f GB", Double(cap) / 1e9))
27
28 // Load the project (package or legacy flat file) into the headless context.
29 let url = URL(fileURLWithPath: path)
30 var jsonURL = url
31 var isDir: ObjCBool = false
32 if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir), isDir.boolValue {
33 jsonURL = url.appendingPathComponent("project.json")
34 }
35 guard let data = try? Data(contentsOf: jsonURL),
36 let doc = try? JSONDecoder().decode(SequencerDocument.self, from: data) else {
37 print("FAIL: cannot read project at \(path)")
38 return
39 }
40 let ctx = DocumentContext.headless
41 ctx.store.adopt(doc.project)
42 let project = ctx.store.project
43 let span = project.clips.map(\.end).max() ?? 0
44 print(String(format: "project: %d clips, %d media, %.0f s timeline",
45 project.clips.count, project.media.count, span))
46
47 // Wait for the launch reconcile so admission is open.
48 let bootDeadline = Date().addingTimeInterval(60)
49 while !pipeline.budgetReady && Date() < bootDeadline { spin(0.1) }
50 guard pipeline.budgetReady else { print("FAIL: ledger reconcile timed out"); return }
51
52 // Playhead stops: spread across the timeline, snapped to the midpoint of a
53 // real video clip so there is always something to cover.
54 var stops: [Double] = []
55 for frac in [0.05, 0.35, 0.65, 0.9] {
56 let t = span * frac
57 if let clip = project.clips
58 .filter({ $0.kind == .video })
59 .min(by: { abs(($0.start + $0.end) / 2 - t) < abs(($1.start + $1.end) / 2 - t) }) {
60 let mid = (clip.start + clip.end) / 2
61 if !stops.contains(where: { abs($0 - mid) < 60 }) { stops.append(mid) }
62 }
63 }
64
65 var failures = 0
66 var walkTick = 0
67 func checkInvariant(_ label: String) {
68 let held = pipeline.ledgerBytes <= cap
69 if !held {
70 failures += 1
71 print(String(format: "FAIL: ledger %.2f GB over cap (%@)",
72 Double(pipeline.ledgerBytes) / 1e9, label))
73 }
74 // The disk walk is the ground truth the ledger approximates. Slack
75 // covers unbudgeted small writes (filmstrips) between reconciles.
76 walkTick += 1
77 if walkTick % 5 == 0 {
78 let disk = MediaPipeline.directorySize(pipeline.cacheRoot)
79 if disk > cap + 500_000_000 {
80 failures += 1
81 print(String(format: "FAIL: %.2f GB on disk exceeds cap (%@)",
82 Double(disk) / 1e9, label))
83 }
84 }
85 }
86
87 for (i, t) in stops.enumerated() {
88 print(String(format: "\n-- stop %d: playhead %.0f s", i + 1, t))
89 ctx.playback.seek(to: t)
90 ctx.chunks.ensure(for: project)
91 ctx.chunks.updateDemand(force: true)
92 let clipsHere = project.clips.filter {
93 $0.kind == .video && $0.start <= t && t < $0.end
94 }
95 let deadline = Date().addingTimeInterval(150)
96 var covered = false
97 var announced = false
98 while Date() < deadline {
99 spin(1.0)
100 checkInvariant("stop \(i + 1)")
101 var missing: [String] = []
102 for clip in clipsHere {
103 guard let media = project.media(clip.mediaId) else { continue }
104 let src = clip.srcIn + (t - clip.start) * clip.speed
105 if !ctx.chunks.isCovered(media: media, sourceTime: src)
106 && !ctx.chunks.buildFailed(media: media, sourceTime: src) {
107 missing.append("\(media.cacheKey.prefix(8))#\(ChunkManager.chunkIndex(forSource: src))")
108 }
109 }
110 covered = missing.isEmpty
111 if covered { break }
112 if !announced {
113 announced = true
114 NSLog("[cachetest] stop %d waiting on: %@", i + 1,
115 missing.joined(separator: " "))
116 }
117 }
118 print(String(format: "coverage at playhead: %@ (ledger %.2f GB)",
119 covered ? "OK" : "FAIL (not covered in 150s)",
120 Double(pipeline.ledgerBytes) / 1e9))
121 if !covered { failures += 1 }
122 }
123
124 // Let any in-flight builds settle, then final ground-truth comparison.
125 spin(5)
126 let disk = MediaPipeline.directorySize(pipeline.cacheRoot)
127 print(String(format: "\nfinal: ledger %.2f GB, disk %.2f GB, cap %.1f GB",
128 Double(pipeline.ledgerBytes) / 1e9, Double(disk) / 1e9,
129 Double(cap) / 1e9))
130 if disk > cap + 500_000_000 { failures += 1; print("FAIL: final disk size over cap") }
131 print(failures == 0 ? "\n== cachetest PASS ==" : "\n== cachetest FAIL (\(failures)) ==")
132}
sequencer/Sources/Sequencer/ChunkedProxy.swift+874-110
......@@ -36,11 +36,18 @@ final class ChunkManager {
3636 /// fixed 960 until the viewer measures itself.
3737 private(set) var previewTargetWidth = 960
3838
39 /// Hard upper bound on proxy resolution: 1080p-wide. A proxy only has to be
40 /// sharp enough to edit against, not master from — and a 4K source is ~8×
41 /// the pixels (and cache bytes) of 1080p for detail an editing preview can't
42 /// use. (It also caps content that was cheaply up-scaled to 4K — e.g. 800×600
43 /// gameplay blown up to 4K — back to a size that reflects its real detail.)
44 static let maxProxyWidth = 1920
45
3946 /// The width a fresh proxy for this media should reach — the preview target,
40 /// never upscaled past the source.
47 /// never upscaled past the source, and never above the 1080p cap.
4148 private func targetWidth(for media: MediaItem) -> Int {
4249 let native = media.width > 0 ? media.width : previewTargetWidth
43 return min(native, previewTargetWidth)
50 return min(native, previewTargetWidth, Self.maxProxyWidth)
4451 }
4552
4653 /// Effective encode width at `level` for this media (even, ffmpeg-friendly).
......@@ -50,9 +57,28 @@ final class ChunkManager {
5057 return max(160, w - (w % 2))
5158 }
5259
60 /// Whether a chunk already on disk at width `built` should be re-encoded:
61 /// either it's below the current sharpness target (sharpen up toward it), or
62 /// it's above the hard 1080p cap (a legacy 4K proxy to shrink back down — the
63 /// cap is constant, so this converges and never churns on window resize). The
64 /// `attempted` guard stops a failed re-encode from looping.
65 private func needsReencode(built: Int, attempted: Int?, target: Int) -> Bool {
66 if built < target { return (attempted ?? 0) < target }
67 if built > Self.maxProxyWidth { return (attempted ?? built) > Self.maxProxyWidth }
68 return false
69 }
70
71 /// A short "rescue" slice standing in for a chunk the playhead landed on
72 /// cold: only `dur` seconds starting `offset` into the chunk's grid slot
73 /// exist on disk (as `rNNNNNN.mov`). Session-only — never persisted; stale
74 /// slice files from a crash are purged by the launch reconcile walk.
75 struct Rescue { let offset: Double; let dur: Double; let width: Int }
76
5377 private struct MediaState {
5478 var built: [Int: Int] = [:] // chunk index → effective width on disk
5579 var attempted: [Int: Int] = [:] // chunk index → width of the last attempt
80 var partial: [Int: Rescue] = [:] // chunk index → rescue slice on disk
81 var rescueAttempted: Set<Int> = []
5682 var inFlight: Set<Int> = []
5783 var failed: Set<Int> = []
5884 var urgent: [Int] = []
......@@ -79,6 +105,18 @@ final class ChunkManager {
79105 /// queue is retained, resuming where it left off. Main-thread only.
80106 private(set) var isPaused = false
81107
108 /// Set when the owning document closes. In-flight builds run off-main and
109 /// their completions land back on main; a completion (via `pump`) reaches
110 /// `ctx`, which is `unowned` and may already be gone once the document is
111 /// torn down. `stopped` makes every ctx-touching entry point a no-op, so a
112 /// build finishing after close can't trap on a dangling context.
113 private var stopped = false
114 func stop() {
115 stopped = true
116 geometryPending?.cancel()
117 geometryPending = nil
118 }
119
82120 /// Toggle proxy optimization on/off (driven by the status-bar readout).
83121 func setPaused(_ paused: Bool) {
84122 guard paused != isPaused else { return }
......@@ -110,9 +148,7 @@ final class ChunkManager {
110148 init() {
111149 NotificationCenter.default.addObserver(
112150 forName: .projectChanged, object: nil, queue: .main) { [weak self] _ in
113 guard let self else { return }
114 self.ensure(for: self.ctx.store.project)
115 self.updateDemand(force: true)
151 self?.scheduleRebuild()
116152 }
117153 // Hiding/focusing a track changes what to optimize first.
118154 NotificationCenter.default.addObserver(
......@@ -121,6 +157,26 @@ final class ChunkManager {
121157 }
122158 }
123159
160 private var geometryPending: DispatchWorkItem?
161 /// Coalesce project-geometry rebuilds. During a continuous drag
162 /// `.projectChanged` fires ~60×/s; rebuilding the whole background queue each
163 /// time is pure waste — moving a clip in TIME doesn't change which source
164 /// chunks it needs. Debounce so the queue rebuilds once the edit settles.
165 /// (Initial load calls `ensure` directly via `startServices`, and playback
166 /// refreshes demand every tick, so nothing waits on this.)
167 private func scheduleRebuild() {
168 guard !stopped else { return }
169 geometryPending?.cancel()
170 let w = DispatchWorkItem { [weak self] in
171 guard let self, !self.stopped else { return }
172 self.geometryPending = nil
173 self.ensure(for: self.ctx.store.project)
174 self.updateDemand(force: true)
175 }
176 geometryPending = w
177 DispatchQueue.main.asyncAfter(deadline: .now() + 0.12, execute: w)
178 }
179
124180 // MARK: - Paths
125181
126182 private func chunksDir(_ key: String) -> URL {
......@@ -135,6 +191,9 @@ final class ChunkManager {
135191 private func chunkURL(key: String, index: Int) -> URL {
136192 chunksDir(key).appendingPathComponent(String(format: "c%06d.mov", index))
137193 }
194 private func rescueURL(key: String, index: Int) -> URL {
195 chunksDir(key).appendingPathComponent(String(format: "r%06d.mov", index))
196 }
138197 private func manifestURL(_ key: String) -> URL {
139198 chunksDir(key).appendingPathComponent("widths.json")
140199 }
......@@ -172,6 +231,15 @@ final class ChunkManager {
172231 var s = states[media.cacheKey] ?? MediaState()
173232 if !s.scanned {
174233 s.scanned = true
234 // Cold big network media starts the realtime ladder two rungs
235 // down: the first urgent (playhead) build must land in seconds,
236 // and a full-target encode of 4K source rarely does — the
237 // adaptive controller would only learn that AFTER the user
238 // stared at a placeholder. It climbs back once builds measure
239 // comfortably fast; background upgrades restore full quality.
240 if media.width >= 2560, Self.isNetworkPath(media.path) {
241 s.qualityIndex = 2
242 }
175243 let widths = loadWidths(media.cacheKey)
176244 if let names = try? FileManager.default
177245 .contentsOfDirectory(atPath: chunksDir(media.cacheKey).path) {
......@@ -205,28 +273,113 @@ final class ChunkManager {
205273 guard s.urgent != wanted else { return }
206274 s.urgent = wanted
207275 states[media.cacheKey] = s
276 windowStarved = false // fresh urgency → re-attempt admission
277 pump()
278 }
279
280 /// A live trim/slip drag is exposing this source moment at a clip edge —
281 /// start building its chunk (and the neighbor in the drag direction)
282 /// BEFORE mouse-up, so a newly extended range is covered by the time the
283 /// user plays across it. Cheap and self-deduping; safe to call per drag
284 /// tick.
285 func noteGestureExposure(media: MediaItem, sourceTime: Double, direction: Int) {
286 guard !stopped, media.duration > 0, !media.isAudio, !hasFullProxy(media) else { return }
287 var s = state(for: media)
288 let n = Self.chunkCount(duration: media.duration)
289 let i = min(n - 1, max(0, Self.chunkIndex(forSource: sourceTime)))
290 let wanted = [i, i + (direction < 0 ? -1 : 1)].filter {
291 $0 >= 0 && $0 < n && s.built[$0] == nil
292 && !s.inFlight.contains($0) && !s.failed.contains($0)
293 }
294 guard s.urgent != wanted else { return }
295 s.urgent = wanted
296 states[media.cacheKey] = s
297 windowStarved = false // fresh urgency → re-attempt admission
208298 pump()
209299 }
210300
211301 /// Rebuild the background fill queue from the project: every chunk in
212 /// every clip's used source range, in order.
302 /// every clip's used source range. Cut heads first (the first chunk of
303 /// every clip is the landing pad for clip-to-clip navigation — a sliver
304 /// of the bytes for most of the "timeline feels instant" effect), then
305 /// everything else; both passes nearest-the-playhead first, so the fill
306 /// grows the working set outward instead of marching from t=0.
213307 func ensure(for project: ProjectModel) {
308 guard !stopped else { return }
309 let ph = ctx.playback.playhead
214310 for media in project.media {
215311 guard media.duration > 0, !media.isAudio, !hasFullProxy(media) else { continue }
216312 var s = state(for: media)
217313 let n = Self.chunkCount(duration: media.duration)
218 var order: [Int] = []
314 var heads: [(i: Int, d: Double)] = []
315 var rest: [(i: Int, d: Double)] = []
316 var seen = Set<Int>()
219317 for clip in project.clips where clip.mediaId == media.id {
220318 let a = min(n - 1, Self.chunkIndex(forSource: clip.srcIn))
221319 let b = min(n - 1, Self.chunkIndex(forSource: clip.srcIn + clip.duration - 0.001))
222 for i in a...max(a, b) where !order.contains(i) { order.append(i) }
320 let d = abs(clip.start - ph)
321 for i in a...max(a, b) where seen.insert(i).inserted {
322 if i == a { heads.append((i, d)) } else { rest.append((i, d)) }
323 }
223324 }
224 s.background = order
325 heads.sort { $0.d < $1.d }
326 rest.sort { $0.d < $1.d }
327 s.background = heads.map(\.i) + rest.map(\.i)
225328 states[media.cacheKey] = s
226329 }
330 // Geometry changed: clips removed from the timeline may have freed
331 // evictable chunks, so a starved fill is worth one more attempt.
332 fillStarved = false
333 // The scan above is also what makes this document's chunks visible as
334 // eviction candidates. If the cache is over cap (launch reconcile ran
335 // before any document had scanned — nothing was evictable then), this
336 // is the moment eviction can actually see the cold chunks: re-check.
337 MediaPipeline.shared.evictIfNeeded()
227338 pump()
228339 }
229340
341 // MARK: - Edit locus
342
343 /// Timeline positions of recent edits, oldest first. Editors scrub and
344 /// re-play around where they're cutting, so chunks near these positions
345 /// build early and evict late. Fed by `Store` after each committed edit;
346 /// entries expire after ~15 minutes.
347 private var editLoci: [(time: Double, at: Date)] = []
348
349 func noteEdits(times: [Double]) {
350 guard !stopped, !times.isEmpty else { return }
351 let now = Date()
352 for t in times {
353 if let i = editLoci.firstIndex(where: { abs($0.time - t) < 30 }) {
354 editLoci[i] = (t, now)
355 } else {
356 editLoci.append((t, now))
357 }
358 }
359 if editLoci.count > 8 { editLoci.removeFirst(editLoci.count - 8) }
360 updateDemand(force: true)
361 }
362
363 /// Loci still fresh enough to matter.
364 private func activeEditLoci() -> [Double] {
365 let cutoff = Date().addingTimeInterval(-15 * 60)
366 editLoci.removeAll { $0.at < cutoff }
367 return editLoci.map(\.time)
368 }
369
370 /// The timeline window the user can currently SEE (when zoomed in enough
371 /// to be meaningful) — scrubbing happens inside it. Set by TimelineView.
372 private func visibleWindow() -> ClosedRange<Double>? {
373 guard let r = ctx.session.visibleTimeRange,
374 r.upperBound - r.lowerBound <= 600 else { return nil }
375 return r
376 }
377
378 /// Distance from a timeline interval to a point (0 when inside).
379 private static func dist(_ t: Double, _ lo: Double, _ hi: Double) -> Double {
380 t < lo ? lo - t : (t > hi ? t - hi : 0)
381 }
382
230383 // MARK: - Prefetch demand (what to optimize first)
231384
232385 private struct DemandKey: Hashable { let key: String; let index: Int }
......@@ -237,6 +390,9 @@ final class ChunkManager {
237390 /// build at the adaptive realtime quality so they land in time; everything
238391 /// else builds at the full preview-quality target.
239392 private var demandImminent: Set<DemandKey> = []
393 /// Uncovered chunks the playhead is sitting INSIDE right now, mapped to the
394 /// source time being shown — the trigger (and anchor) for rescue slices.
395 private var demandRescue: [DemandKey: Double] = [:]
240396 private var lastDemandPlayhead = -1e9
241397 private var lastDemandSign = 0.0
242398
......@@ -249,14 +405,16 @@ final class ChunkManager {
249405 private static let imminentAhead = 45.0
250406
251407 /// Recompute the build order — the heart of "optimize the right thing first."
252 /// For every video clip near the playhead we score the proxy chunks its
408 /// For every video clip near an ANCHOR we score the proxy chunks its
253409 /// source range needs and sort them: coverage before sharpening, visible
254 /// (and focused) tracks before hidden, and nearer the playhead in the
255 /// playback direction before farther. Chunks outside the window fall through
256 /// to the whole-project background queue (`ensure`). Cheap; safe to call as
257 /// the playhead moves (self-throttled).
410 /// (and focused) tracks before hidden, and nearer the anchor before
411 /// farther. Anchors, hottest first: the playhead (direction-weighted),
412 /// recent edit sites, and the visible timeline window — the places the
413 /// user is most likely to play next. Chunks outside every window fall
414 /// through to the whole-project background queue (`ensure`). Cheap; safe
415 /// to call as the playhead moves (self-throttled).
258416 func updateDemand(force: Bool = false) {
259 guard ctx != nil else { return }
417 guard !stopped else { return }
260418 let ph = ctx.playback.playhead
261419 let sign = ctx.playback.rate < 0 ? -1.0 : 1.0
262420 guard force || abs(ph - lastDemandPlayhead) > 1.5 || sign != lastDemandSign else { return }
......@@ -267,48 +425,97 @@ final class ChunkManager {
267425 let dir = sign
268426 let anyFocused = !ctx.session.focusedTracks.isEmpty
269427
428 // Secondary anchors: recent edit sites, then the visible window.
429 // Their bias keeps them strictly behind playhead-window work but far
430 // ahead of the whole-project background fill.
431 struct Window { let lo: Double; let hi: Double; let anchor: Double; let bias: Double }
432 var windows = [Window(lo: ph - (dir > 0 ? Self.prefetchBehind : Self.prefetchAhead),
433 hi: ph + (dir > 0 ? Self.prefetchAhead : Self.prefetchBehind),
434 anchor: ph, bias: 0)]
435 for l in activeEditLoci() {
436 windows.append(Window(lo: l - 45, hi: l + 45, anchor: l, bias: 2_000))
437 }
438 if let vis = visibleWindow() {
439 windows.append(Window(lo: vis.lowerBound, hi: vis.upperBound,
440 anchor: (vis.lowerBound + vis.upperBound) / 2, bias: 6_000))
441 }
442
270443 struct Cand { let key: DemandKey; let score: Double; let imminent: Bool }
271444 var cands: [Cand] = []
445 var rescue: [DemandKey: Double] = [:]
272446 for clip in project.clips where clip.kind == .video {
273447 guard let media = project.media(clip.mediaId), media.duration > 0,
274448 !media.isAudio, !hasFullProxy(media) else { continue }
275449 let visible = !ctx.session.hiddenTracks.contains(clip.track)
276450 let focused = ctx.session.focusedTracks.contains(clip.track)
277 let lo = ph - (dir > 0 ? Self.prefetchBehind : Self.prefetchAhead)
278 let hi = ph + (dir > 0 ? Self.prefetchAhead : Self.prefetchBehind)
279 let a = max(clip.start, lo), b = min(clip.end, hi)
280 guard b > a else { continue }
281 let n = Self.chunkCount(duration: media.duration)
282 let s = state(for: media)
283 let target = targetWidth(for: media)
284 let srcA = clip.srcIn + (a - clip.start) * clip.speed
285 let srcB = clip.srcIn + (b - clip.start) * clip.speed
286 let ci = min(n - 1, Self.chunkIndex(forSource: min(srcA, srcB)))
287 let cj = min(n - 1, Self.chunkIndex(forSource: max(srcA, srcB) - 1e-6))
288 for idx in ci...max(ci, cj) {
289 if (s.built[idx] ?? 0) >= target { continue } // already good enough
290 let covered = s.built[idx] != nil
291 // Timeline moment this chunk's content plays inside this clip.
292 let tl = clip.start
293 + (Double(idx) * Self.chunkSeconds - clip.srcIn) / max(1e-4, clip.speed)
294 let signed = (min(max(tl, a), b) - ph) * dir // >0 = ahead
295 var score = signed >= 0 ? signed : -signed * 4 // behind costs 4×
296 if covered { score += 10_000 } // coverage beats sharpening
297 if !visible { score += 100_000 } // hidden tracks last
298 else if anyFocused && !focused { score += 1_000 } // the enlarged pane first
299 let imminent = visible && !covered && signed >= 0 && signed < Self.imminentAhead
300 cands.append(Cand(key: DemandKey(key: media.cacheKey, index: idx),
301 score: score, imminent: imminent))
451 var n = 0, target = 0
452 var s: MediaState?
453 for w in windows {
454 let a = max(clip.start, w.lo), b = min(clip.end, w.hi)
455 guard b > a else { continue }
456 if s == nil { // lazy: only scan media that some window needs
457 s = state(for: media)
458 n = Self.chunkCount(duration: media.duration)
459 target = targetWidth(for: media)
460 }
461 guard let st = s else { continue }
462 let srcA = clip.srcIn + (a - clip.start) * clip.speed
463 let srcB = clip.srcIn + (b - clip.start) * clip.speed
464 let ci = min(n - 1, Self.chunkIndex(forSource: min(srcA, srcB)))
465 let cj = min(n - 1, Self.chunkIndex(forSource: max(srcA, srcB) - 1e-6))
466 for idx in ci...max(ci, cj) {
467 if (st.built[idx] ?? 0) >= target { continue } // already good enough
468 let covered = st.built[idx] != nil
469 // Timeline interval this chunk's content plays inside this
470 // clip. Scoring by the INTERVAL (not the chunk's start
471 // moment) is what puts the chunk UNDER the playhead at
472 // score 0 — its start is always "behind", and treating it
473 // that way made the builder prefetch a dozen ahead-chunks
474 // while the user stared at "Loading Media…" on the frame
475 // they'd actually landed on.
476 let t0 = clip.start
477 + (Double(idx) * Self.chunkSeconds - clip.srcIn) / max(1e-4, clip.speed)
478 let t1 = clip.start
479 + (Double(idx + 1) * Self.chunkSeconds - clip.srcIn) / max(1e-4, clip.speed)
480 let cLo = max(min(t0, t1), a), cHi = min(max(t0, t1), b)
481 let isPlayhead = w.bias == 0
482 var score: Double
483 var under = false
484 if isPlayhead {
485 let ahead = dir > 0 ? cLo - ph : ph - cHi // >0 = strictly ahead
486 let behind = dir > 0 ? ph - cHi : cLo - ph // >0 = strictly behind
487 under = ahead <= 0 && behind <= 0 // playing right now
488 score = ahead > 0 ? ahead : (behind > 0 ? behind * 4 : 0)
489 } else {
490 score = w.bias + max(0, max(cLo - w.anchor, w.anchor - cHi))
491 }
492 if covered { score += 10_000 } // coverage beats sharpening
493 if !visible { score += 100_000 } // hidden tracks last
494 else if anyFocused && !focused { score += 1_000 } // the enlarged pane first
495 let ahead = dir > 0 ? cLo - ph : ph - cHi
496 let imminent = isPlayhead && visible && !covered
497 && (under || (ahead >= 0 && ahead < Self.imminentAhead))
498 let dk = DemandKey(key: media.cacheKey, index: idx)
499 if isPlayhead, visible, under, !covered {
500 rescue[dk] = clip.srcIn + (ph - clip.start) * clip.speed
501 }
502 cands.append(Cand(key: dk, score: score, imminent: imminent))
503 }
302504 }
303505 }
304506 cands.sort { $0.score < $1.score }
507 let oldDemand = demand
305508 demand.removeAll(keepingCapacity: true)
306509 demandImminent.removeAll(keepingCapacity: true)
510 demandRescue = rescue
307511 var seen = Set<DemandKey>()
308512 for c in cands where seen.insert(c.key).inserted {
309513 demand.append(c.key)
310514 if c.imminent { demandImminent.insert(c.key) }
311515 }
516 // The window moved: what starved before may fit now (different chunks,
517 // and colder ones may have fallen out of the protected radius).
518 if demand != oldDemand { windowStarved = false }
312519 pump()
313520 }
314521
......@@ -318,7 +525,13 @@ final class ChunkManager {
318525 if media.isAudio { return true } // audio plays the original directly
319526 if hasFullProxy(media) { return true }
320527 let s = state(for: media)
321 return s.built[Self.chunkIndex(forSource: sourceTime)] != nil
528 let i = Self.chunkIndex(forSource: sourceTime)
529 if s.built[i] != nil { return true }
530 if let p = s.partial[i] { // a rescue slice covers only part of the slot
531 let t = sourceTime - Double(i) * Self.chunkSeconds
532 return t >= p.offset - 0.05 && t <= p.offset + p.dur - 0.05
533 }
534 return false
322535 }
323536
324537 /// Last known answer; unknown kicks the async composition build (which
......@@ -330,6 +543,196 @@ final class ChunkManager {
330543 return false
331544 }
332545
546 // MARK: - Cache budget (admission + eviction support)
547
548 /// Set when a build was skipped because the cache is at its cap and
549 /// nothing colder could be evicted to make room. `window` covers the
550 /// playhead prefetch; `fill` the whole-project background queue. Cleared
551 /// whenever the budget or the demand changes.
552 private var windowStarved = false
553 private var fillStarved = false
554 /// The cache is full and optimization is deliberately not building
555 /// everything — drives the status-bar messaging.
556 var budgetStarved: Bool { windowStarved || fillStarved }
557
558 /// The global cache budget moved (reconcile finished, eviction freed
559 /// space, cap changed): try again from a clean slate.
560 func budgetChanged() {
561 guard !stopped else { return }
562 windowStarved = false
563 fillStarved = false
564 pump()
565 }
566
567 /// `SEQ_BUILDLOG=1`: log every build START and admission failure — the
568 /// queue's decisions, not just its results. For chasing "why isn't chunk
569 /// X building" (success completions are otherwise silent).
570 static let buildLog = ProcessInfo.processInfo.environment["SEQ_BUILDLOG"] != nil
571
572 /// Global correction factor: measured chunk bytes ÷ raw estimate, EMA'd.
573 /// Starts at 1 (the raw model is a ProRes-proxy ballpark) and converges on
574 /// the actual footage within a few chunks.
575 private static var chunkRateEMA = 1.0
576
577 /// Ballpark bytes for a chunk at `width` before correction: ProRes proxy
578 /// ≈ 0.09 bytes per pixel per frame, plus PCM audio when present.
579 /// `seconds` overrides the encoded duration (rescue slices).
580 private func rawChunkEstimate(media: MediaItem, width: Int, index: Int,
581 seconds: Double? = nil) -> Double {
582 let dur = seconds ?? min(Self.chunkSeconds,
583 max(1, media.duration - Double(index) * Self.chunkSeconds))
584 let aspect = (media.width > 0 && media.height > 0)
585 ? Double(media.height) / Double(media.width) : 9.0 / 16
586 let fps = min(60.0, max(10.0, media.fps))
587 var b = 0.09 * Double(width) * (Double(width) * aspect) * fps * dur
588 if media.hasAudio { b += 200_000 * dur }
589 return b
590 }
591
592 /// Corrected + safety-margined estimate the admission gate reserves.
593 private func estimateChunkBytes(media: MediaItem, width: Int, index: Int,
594 seconds: Double? = nil) -> Int64 {
595 Int64(rawChunkEstimate(media: media, width: width, index: index, seconds: seconds)
596 * Self.chunkRateEMA * 1.3)
597 }
598
599 /// A cold, already-built chunk the global cache may delete to make room.
600 struct EvictionCandidate {
601 let key: String
602 let index: Int
603 let url: URL
604 /// Timeline seconds from this document's playhead to the nearest use
605 /// of the chunk; 1e12 when no clip uses it at all (media edited off
606 /// the timeline — the coldest bytes there are).
607 let coldness: Double
608 }
609
610 /// Timeline distance within which built chunks are HARD-protected: the
611 /// frames playback will hit imminently. Everything beyond is merely
612 /// ranked by distance — evictable coldest-first — so when the working set
613 /// alone exceeds the cap, it shrinks to fit instead of wedging eviction.
614 private static let hardProtectRadius = 60.0
615
616 /// Every built chunk of this document that eviction MAY delete, scored by
617 /// coldness (timeline distance from the playhead; off-timeline chunks are
618 /// coldest). Hard-excluded: the demand window, anything mid-build or
619 /// urgent, and chunks within `hardProtectRadius` of the playhead — which
620 /// for a background window is exactly its resume neighborhood.
621 func evictionCandidates() -> [EvictionCandidate] {
622 guard !stopped else { return [] }
623 let project = ctx.store.project
624 let ph = ctx.playback.playhead
625 var protected: Set<DemandKey> = Set(demand)
626 for (key, s) in states {
627 for i in s.inFlight { protected.insert(DemandKey(key: key, index: i)) }
628 for i in s.urgent { protected.insert(DemandKey(key: key, index: i)) }
629 }
630 // Score every chunk any clip uses by its distance from the nearest
631 // heat anchor (playhead, then recent edit sites and the visible
632 // window at a penalty so the playhead wins ties); hard-protect only
633 // the playhead-imminent ones. Cut heads read as 4× closer than they
634 // are, so the "instant timeline" landing pads die last.
635 let loci = activeEditLoci()
636 let vis = visibleWindow()
637 var distance: [DemandKey: Double] = [:]
638 var heads: Set<DemandKey> = []
639 for clip in project.clips where clip.kind == .video {
640 guard let media = project.media(clip.mediaId), media.duration > 0,
641 !media.isAudio else { continue }
642 let n = Self.chunkCount(duration: media.duration)
643 let a = min(n - 1, Self.chunkIndex(forSource: clip.srcIn))
644 let b = min(n - 1, Self.chunkIndex(forSource: clip.srcIn + clip.duration - 0.001))
645 heads.insert(DemandKey(key: media.cacheKey, index: a))
646 for i in a...max(a, b) {
647 let t0 = clip.start + (Double(i) * Self.chunkSeconds - clip.srcIn) / max(1e-4, clip.speed)
648 let t1 = clip.start + (Double(i + 1) * Self.chunkSeconds - clip.srcIn) / max(1e-4, clip.speed)
649 let lo = max(clip.start, min(t0, t1)), hi = min(clip.end, max(t0, t1))
650 var d = Self.dist(ph, lo, hi)
651 for l in loci { d = min(d, Self.dist(l, lo, hi) + 60) }
652 if let vis { // gap between the chunk's interval and the visible range
653 let gap = max(0, max(lo - vis.upperBound, vis.lowerBound - hi))
654 d = min(d, gap + 120)
655 }
656 let dk = DemandKey(key: media.cacheKey, index: i)
657 distance[dk] = min(distance[dk] ?? .infinity, d)
658 if Self.dist(ph, lo, hi) < Self.hardProtectRadius { protected.insert(dk) }
659 }
660 }
661 var out: [EvictionCandidate] = []
662 var builtTotal = 0
663 for (key, s) in states {
664 builtTotal += s.built.count
665 for i in s.built.keys {
666 let dk = DemandKey(key: key, index: i)
667 guard !protected.contains(dk), !s.inFlight.contains(i) else { continue }
668 var coldness = distance[dk] ?? 1e12
669 if heads.contains(dk) { coldness *= 0.25 } // landing pads die last
670 out.append(EvictionCandidate(key: key, index: i,
671 url: chunkURL(key: key, index: i),
672 coldness: coldness))
673 }
674 // Rescue slices are evictable like any chunk (the playhead radius
675 // protects the live one); they just live in an rNNNNNN.mov file.
676 for i in s.partial.keys where s.built[i] == nil {
677 let dk = DemandKey(key: key, index: i)
678 guard !protected.contains(dk), !s.inFlight.contains(i) else { continue }
679 out.append(EvictionCandidate(key: key, index: i,
680 url: rescueURL(key: key, index: i),
681 coldness: distance[dk] ?? 1e12))
682 }
683 }
684 NSLog("[cache] candidates: %d of %d built chunks (%d protected, playhead %.0fs)",
685 out.count, builtTotal, protected.count, ph)
686 return out
687 }
688
689 /// The global cache deleted these chunk files: drop them from state and
690 /// bump the version so compositions rebuild onto the original-file
691 /// fallback instead of pointing at deleted movs.
692 func noteEvicted(key: String, indices: [Int]) {
693 guard var s = states[key] else { return }
694 var changed = false
695 for i in indices {
696 if s.built.removeValue(forKey: i) != nil {
697 s.attempted.removeValue(forKey: i)
698 changed = true
699 }
700 if s.partial.removeValue(forKey: i) != nil { changed = true }
701 }
702 guard changed else { return }
703 s.version += 1
704 states[key] = s
705 persistWidths(key: key)
706 }
707
708 /// Chunk states for the timeline's optimization strip — one cheap value
709 /// snapshot per media per strip rebuild. nil when the media hasn't been
710 /// scanned yet (unknown; the strip paints it as unoptimized until the
711 /// initial `ensure` pass scans it).
712 struct StripSnapshot {
713 let n: Int
714 let target: Int
715 let built: [Int: Int]
716 let inFlight: Set<Int>
717 let partial: Set<Int>
718 let failed: Set<Int>
719 let fullProxy: Bool
720 }
721
722 func stripSnapshot(media: MediaItem) -> StripSnapshot? {
723 guard media.duration > 0, !media.isAudio else { return nil }
724 if hasFullProxy(media) {
725 return StripSnapshot(n: 1, target: 0, built: [:], inFlight: [],
726 partial: [], failed: [], fullProxy: true)
727 }
728 guard let s = states[media.cacheKey], s.scanned else { return nil }
729 return StripSnapshot(n: Self.chunkCount(duration: media.duration),
730 target: targetWidth(for: media),
731 built: s.built, inFlight: s.inFlight,
732 partial: Set(s.partial.keys), failed: s.failed,
733 fullProxy: false)
734 }
735
333736 /// (building now, waiting in queue) across all media — for the status bar.
334737 /// A chunk counts as queued while it's either missing OR still below the
335738 /// preview-quality target (i.e. an upgrade is pending).
......@@ -348,8 +751,14 @@ final class ChunkManager {
348751
349752 /// Per-chunk proxy width right now (players record this at item-swap time
350753 /// to judge whether a later swap upgrades the frame under the playhead).
754 /// Rescue slices report as width 1: "something is there", and any full
755 /// build over them reads as a strict upgrade so the player adopts it.
351756 func builtWidths(media: MediaItem) -> [Int: Int] {
352 state(for: media).built
757 let s = state(for: media)
758 guard !s.partial.isEmpty else { return s.built }
759 var w = s.built
760 for i in s.partial.keys where w[i] == nil { w[i] = 1 }
761 return w
353762 }
354763
355764 func builtChunkURL(media: MediaItem, index: Int) -> URL? {
......@@ -357,53 +766,181 @@ final class ChunkManager {
357766 ? chunkURL(key: media.cacheKey, index: index) : nil
358767 }
359768
769 /// A playable (file, time-in-file) pair that shows `sourceTime` — the
770 /// sharpest thing on disk right now: legacy full proxy, built chunk,
771 /// covering rescue slice, or the original when it's known playable. nil
772 /// when this frame genuinely can't be decoded yet. Feeds the RAM frame
773 /// cache's stand-in decodes; main-thread.
774 func frameSource(media: MediaItem, sourceTime: Double) -> (url: URL, time: Double)? {
775 guard !media.isAudio else { return nil }
776 if let proxy = MediaPipeline.shared.proxyURL(for: media) {
777 return (proxy, sourceTime)
778 }
779 let s = state(for: media)
780 let i = Self.chunkIndex(forSource: sourceTime)
781 let local = sourceTime - Double(i) * Self.chunkSeconds
782 if s.built[i] != nil {
783 return (chunkURL(key: media.cacheKey, index: i), local)
784 }
785 if let p = s.partial[i], local >= p.offset, local <= p.offset + p.dur - 0.05 {
786 return (rescueURL(key: media.cacheKey, index: i), local - p.offset)
787 }
788 if s.originalPlayable == true { return (media.url, sourceTime) }
789 return nil
790 }
791
792 /// Rough bytes this project needs to be FULLY optimized — every chunk any
793 /// clip uses, at the current preview target width — and how many bytes its
794 /// media already have on disk. Estimates use the same corrected model as
795 /// the admission gate (sans safety margin); `built` is a real disk walk of
796 /// each media's chunk dir, so call this on demand (Settings), not per frame.
797 func optimizeEstimate(for project: ProjectModel) -> (total: Int64, built: Int64) {
798 var total: Int64 = 0, built: Int64 = 0
799 for media in project.media where !media.isAudio && media.duration > 0 {
800 if hasFullProxy(media), let proxy = MediaPipeline.shared.proxyURL(for: media) {
801 let sz = (try? FileManager.default.attributesOfItem(atPath: proxy.path)[.size]
802 as? NSNumber)?.int64Value ?? 0
803 total += sz
804 built += sz
805 continue
806 }
807 let n = Self.chunkCount(duration: media.duration)
808 var used = Set<Int>()
809 for clip in project.clips where clip.mediaId == media.id && clip.kind == .video {
810 let a = min(n - 1, Self.chunkIndex(forSource: clip.srcIn))
811 let b = min(n - 1, Self.chunkIndex(forSource: clip.srcIn + clip.duration - 0.001))
812 for i in a...max(a, b) { used.insert(i) }
813 }
814 guard !used.isEmpty else { continue }
815 let target = targetWidth(for: media)
816 for i in used {
817 total += Int64(rawChunkEstimate(media: media, width: target, index: i)
818 * Self.chunkRateEMA)
819 }
820 built += MediaPipeline.directorySize(chunksDir(media.cacheKey))
821 }
822 return (total, built)
823 }
824
825 /// One-line explanation of why the frame at `sourceTime` might not be
826 /// showing — the [miss] log's payload. Every "Loading Media…" spinner the
827 /// viewer escalates to gets one of these, so a spinner sighting is
828 /// diagnosable from the log instead of a shrug: was the chunk missing
829 /// entirely (and did the demand scorer even know about it?), mid-build,
830 /// built-but-not-yet-stitched, or built with the player just late?
831 func missDiagnosis(media: MediaItem, sourceTime: Double) -> String {
832 guard !media.isAudio else { return "audio" }
833 guard !hasFullProxy(media) else { return "full-proxy player-late" }
834 let s = state(for: media)
835 let i = Self.chunkIndex(forSource: sourceTime)
836 var bits = ["\(media.cacheKey.prefix(8))#\(i)"]
837 if let w = s.built[i] {
838 bits.append("built(w=\(w))")
839 bits.append(s.compositionVersion != s.version ? "composition-lag"
840 : "player-late")
841 } else if let p = s.partial[i] {
842 let t = sourceTime - Double(i) * Self.chunkSeconds
843 bits.append(String(format: "rescue(%.0fs@%.0fs t=%.1f)", p.dur, p.offset, t))
844 bits.append(s.compositionVersion != s.version ? "composition-lag"
845 : "player-late")
846 } else if s.inFlight.contains(i) {
847 bits.append("building")
848 } else if s.failed.contains(i) {
849 bits.append("build-failed")
850 } else {
851 let dk = DemandKey(key: media.cacheKey, index: i)
852 if let rank = demand.firstIndex(of: dk) {
853 bits.append("queued(demand#\(rank)\(demandImminent.contains(dk) ? " imminent" : ""))")
854 } else if s.background.contains(i) {
855 bits.append("queued(background)") // demand window missed it
856 } else {
857 bits.append("NOT-QUEUED") // heuristic gap — the bad one
858 }
859 }
860 if budgetStarved { bits.append("budget-starved") }
861 if isPaused { bits.append("opt-paused") }
862 if s.originalPlayable == nil { bits.append("orig-unknown") }
863 else if s.originalPlayable == false { bits.append("orig-unplayable") }
864 return bits.joined(separator: " ")
865 }
866
867 /// The proxy chunk covering `sourceTime` tried to build and hard-failed
868 /// (both encoders) with nothing usable — the viewer surfaces this instead of
869 /// spinning "processing…" forever.
870 func buildFailed(media: MediaItem, sourceTime: Double) -> Bool {
871 if media.isAudio || hasFullProxy(media) { return false }
872 return state(for: media).failed.contains(Self.chunkIndex(forSource: sourceTime))
873 }
874
360875 // MARK: - Build queue
361876
877 /// Which budget tier a job builds for. `window` jobs (playhead prefetch,
878 /// urgent coverage) may evict cold chunks of open documents to make room;
879 /// `fill` jobs (whole-project background) may only consume free budget or
880 /// space freed from closed projects — never evict another chunk. That
881 /// asymmetry is what makes the cache converge instead of thrash: a build
882 /// can only displace bytes strictly colder than itself.
883 private enum JobTier { case window, fill }
884
362885 /// Next chunk to build, in priority order:
363886 /// 0. urgent — coverage the playhead needs NOW, at any quality;
364887 /// 1. missing — background chunks not yet built at all (coverage first);
365888 /// 2. upgrade — background chunks built below the preview-quality target.
366889 /// Coverage always beats sharpening, so playback never stalls waiting on a
367890 /// quality upgrade of a frame that's already visible.
368 private func nextJob() -> (media: MediaItem, index: Int, urgent: Bool)? {
891 private func nextJob(frontDoc: Bool) -> (media: MediaItem, index: Int, urgent: Bool, tier: JobTier)? {
892 // Only the frontmost document builds proxies. macOS state restoration
893 // reopens every previously-open project on launch; if each one built its
894 // proxies, they'd transcode their full ProRes sets in parallel. Every open
895 // project's media counts as "in use", so eviction can't reclaim any of it —
896 // the shared cache blows past its cap and fills the disk (the reported bug).
897 // A background project builds nothing until you switch to it (its window
898 // becoming main re-pumps it — see windowDidBecomeMain); the document under
899 // the playhead is always the front one, so playback is unaffected.
900 guard frontDoc else { return nil }
369901 // 1. Prefetch demand — already ordered best-first (visible/focused, near,
370902 // coverage before sharpening). Coverage rides the adaptive realtime
371903 // quality when imminent; an upgrade goes for the full target.
372 for dk in demand {
373 guard let media = mediaByKey[dk.key] else { continue }
374 let s = states[dk.key] ?? state(for: media)
375 guard !s.inFlight.contains(dk.index), !s.failed.contains(dk.index) else { continue }
376 let target = targetWidth(for: media)
377 if let have = s.built[dk.index] {
378 if have < target, (s.attempted[dk.index] ?? 0) < target {
379 return (media, dk.index, false) // upgrade → target
904 if !windowStarved {
905 for dk in demand {
906 guard let media = mediaByKey[dk.key] else { continue }
907 let s = states[dk.key] ?? state(for: media)
908 guard !s.inFlight.contains(dk.index), !s.failed.contains(dk.index) else { continue }
909 let target = targetWidth(for: media)
910 if let have = s.built[dk.index] {
911 if needsReencode(built: have, attempted: s.attempted[dk.index], target: target) {
912 return (media, dk.index, false, .window) // sharpen or shrink → target
913 }
914 } else {
915 return (media, dk.index, demandImminent.contains(dk), .window) // coverage
380916 }
381 } else {
382 return (media, dk.index, demandImminent.contains(dk)) // coverage
383917 }
384 }
385 // 2. Legacy urgent (headless `want`) then the whole-project background
386 // fill for chunks off-screen of the prefetch window.
387 for (key, s) in states {
388 guard let media = mediaByKey[key] else { continue }
389 for i in s.urgent where s.built[i] == nil
390 && !s.inFlight.contains(i) && !s.failed.contains(i) {
391 return (media, i, true)
918 // 2. Legacy urgent (headless `want`).
919 for (key, s) in states {
920 guard let media = mediaByKey[key] else { continue }
921 for i in s.urgent where s.built[i] == nil
922 && !s.inFlight.contains(i) && !s.failed.contains(i) {
923 return (media, i, true, .window)
924 }
392925 }
393926 }
927 // 3. Whole-project background fill for chunks off-screen of the
928 // prefetch window — the tier that stops when the cache is full.
929 guard !fillStarved else { return nil }
394930 for (key, s) in states {
395931 guard let media = mediaByKey[key] else { continue }
396932 for i in s.background where s.built[i] == nil
397933 && !s.inFlight.contains(i) && !s.failed.contains(i) {
398 return (media, i, false)
934 return (media, i, false, .fill)
399935 }
400936 }
401937 for (key, s) in states {
402938 guard let media = mediaByKey[key] else { continue }
403939 let target = targetWidth(for: media)
404940 for i in s.background where !s.inFlight.contains(i) {
405 if let have = s.built[i], have < target, (s.attempted[i] ?? 0) < target {
406 return (media, i, false)
941 if let have = s.built[i],
942 needsReencode(built: have, attempted: s.attempted[i], target: target) {
943 return (media, i, false, .fill)
407944 }
408945 }
409946 }
......@@ -411,40 +948,161 @@ final class ChunkManager {
411948 }
412949
413950 private func pump() {
951 guard !stopped else { return } // document closing — don't touch ctx
414952 // Paused stops idle background fill, but playback still optimizes the
415953 // chunks it's about to need.
954 // Only the front document runs its whole-project background fill (see
955 // nextJob) — this is the guard that stops N restored projects transcoding
956 // their full proxy sets in parallel and overflowing the cache.
957 let frontDoc = DocumentContext.current === ctx
416958 while (!isPaused || ctx.playback.isPlaying),
417 activeBuilds < maxBuilds, let job = nextJob() {
418 // During playback keep one slot free for urgent coverage so a slow
419 // background quality-upgrade can't stall the frames being played.
420 if !job.urgent, ctx.playback.isPlaying, activeBuilds >= maxBuilds - 1 { break }
421 let (media, index, urgent) = job
959 activeBuilds < maxBuilds + 1, let job = nextJob(frontDoc: frontDoc) {
960 // URGENT coverage (the playhead just landed on/near this chunk) may
961 // take one OVERFLOW slot: a couple of minutes-long 4K background
962 // encodes must never wall off the frame the user is looking at —
963 // that wait was the last reproducible "Loading Media…" spinner.
964 // Everything else respects maxBuilds, and during playback keeps one
965 // slot free for urgent coverage on top.
966 if !job.urgent {
967 if activeBuilds >= maxBuilds { break }
968 if ctx.playback.isPlaying, activeBuilds >= maxBuilds - 1 { break }
969 }
970 let (media, index, urgent, tier) = job
971 // RESCUE: the playhead is sitting on this uncovered chunk right
972 // now. A full 30-second encode makes the user wait for content
973 // they're already staring at — so first land a short slice
974 // starting AT the playhead (read-bound NAS sources scale with
975 // encoded seconds, so this is fast even when quality drops
976 // aren't), then immediately re-queue the full chunk behind it.
977 var slice: (offset: Double, dur: Double)? = nil
978 let dk = DemandKey(key: media.cacheKey, index: index)
979 if urgent, let st = states[media.cacheKey], st.built[index] == nil,
980 st.partial[index] == nil, !st.rescueAttempted.contains(index),
981 let srcT = demandRescue[dk] {
982 let chunkStart = Double(index) * Self.chunkSeconds
983 let content = min(Self.chunkSeconds, media.duration - chunkStart)
984 let offset = min(max(0, (srcT - chunkStart - 1).rounded(.down)),
985 max(0, content - 2))
986 let dur = min(Self.rescueSeconds, content - offset)
987 // Only worth two encodes when the slice is a real shortcut.
988 if dur >= 4, dur <= content - offset, dur < content * 0.7 {
989 slice = (offset, dur)
990 }
991 }
422992 // Urgent builds ride the adaptive realtime level; background builds
423 // go for the full preview-quality target.
424 let level = urgent ? (states[media.cacheKey]?.qualityIndex ?? 0) : 0
993 // go for the full preview-quality target. A rescue slice drops one
994 // more rung — landing NOW is its whole purpose.
995 var level = urgent ? (states[media.cacheKey]?.qualityIndex ?? 0) : 0
996 if slice != nil { level = min(Self.qualities.count - 1, level + 1) }
425997 let width = buildWidth(level: level, media: media)
426998 let fpsDiv = Self.qualities[min(max(0, level), Self.qualities.count - 1)].fpsDivisor
427999 let fps = max(1, Int((media.fps / Double(fpsDiv)).rounded()))
1000 // Admission gate: the cap is enforced BEFORE bytes hit the disk.
1001 // No reservation, no build — first try to make room by evicting
1002 // strictly-colder bytes (closed projects for any tier; open
1003 // documents' cold chunks only for window builds), and if nothing
1004 // colder exists, this tier starves until the budget changes.
1005 let est = estimateChunkBytes(media: media, width: width, index: index,
1006 seconds: slice?.dur)
1007 let ceiling = tier == .window ? MediaPipeline.shared.maxCacheBytes
1008 : MediaPipeline.shared.lowWatermarkBytes
1009 if !MediaPipeline.shared.tryReserve(bytes: est, upTo: ceiling) {
1010 if Self.buildLog {
1011 SeqLog.log("[cache] admission blocked %@#%d est=%.0fMB tier=%@",
1012 String(media.cacheKey.prefix(8)), index,
1013 Double(est) / 1e6, tier == .window ? "window" : "fill")
1014 }
1015 // Try to make room by evicting strictly-colder bytes: closed
1016 // projects' dirs for any tier; open documents' cold chunks
1017 // only for window builds (fill must never displace a chunk —
1018 // it stops at the watermark instead, which is what keeps
1019 // fill and eviction from fighting over the same bytes).
1020 MediaPipeline.shared.evictToFit(need: est, openDocChunks: tier == .window,
1021 upTo: ceiling) { [weak self] ok in
1022 guard let self, !self.stopped, !ok else { return }
1023 // Eviction couldn't free enough (or one is already running
1024 // — resolved via budgetChanged when it lands): starve the
1025 // tier so pump stops retrying until the budget moves.
1026 if tier == .window { self.windowStarved = true }
1027 else { self.fillStarved = true }
1028 NotificationCenter.default.post(name: .mediaStatusChanged, object: nil)
1029 }
1030 break
1031 }
4281032 states[media.cacheKey]?.inFlight.insert(index)
429 states[media.cacheKey]?.attempted[index] = width
1033 if slice == nil {
1034 states[media.cacheKey]?.attempted[index] = width
1035 } else {
1036 states[media.cacheKey]?.rescueAttempted.insert(index)
1037 }
4301038 activeBuilds += 1
1039 if Self.buildLog {
1040 SeqLog.log("[cache] start %@#%d w=%d urgent=%d tier=%@%@",
1041 String(media.cacheKey.prefix(8)), index, width, urgent ? 1 : 0,
1042 tier == .window ? "window" : "fill",
1043 slice != nil ? " rescue" : "")
1044 }
4311045 DispatchQueue.global(qos: .userInitiated).async { [self] in
432 let r = buildChunk(media: media, index: index, width: width, fps: fps)
1046 let r = buildChunk(media: media, index: index, width: width, fps: fps,
1047 slice: slice)
4331048 DispatchQueue.main.async {
1049 if !r.ok {
1050 SeqLog.log("[cache] build FAILED %@#%d w=%d%@",
1051 String(media.cacheKey.prefix(8)), index, width,
1052 slice != nil ? " (rescue)" : "")
1053 }
1054 MediaPipeline.shared.commitBuild(
1055 reserved: est, delta: r.ok ? r.newBytes - r.oldBytes : 0)
1056 if r.ok, r.newBytes > 0, slice == nil {
1057 // Fold the measured size into the estimator (clamped so
1058 // one weird chunk can't poison admissions).
1059 let raw = self.rawChunkEstimate(media: media, width: width, index: index)
1060 if raw > 0 {
1061 let ratio = Double(r.newBytes) / raw
1062 Self.chunkRateEMA = min(10, max(0.1,
1063 Self.chunkRateEMA * 0.7 + ratio * 0.3))
1064 }
1065 }
4341066 self.activeBuilds -= 1
4351067 var s = self.states[media.cacheKey] ?? MediaState()
4361068 s.inFlight.remove(index)
437 if r.ok {
1069 if r.ok, let slice {
1070 // Slice landed: cover the playhead NOW; the chunk still
1071 // reads as unbuilt so the full encode queues right behind.
1072 s.partial[index] = Rescue(offset: slice.offset, dur: r.dur,
1073 width: width)
1074 s.failed.remove(index)
1075 s.version += 1
1076 SeqLog.log("[cache] rescue %@#%d %.0fs@%.0fs w=%d in %.1fs",
1077 String(media.cacheKey.prefix(8)), index, r.dur,
1078 slice.offset, width, r.wall)
1079 } else if r.ok {
4381080 s.built[index] = width
4391081 s.failed.remove(index)
4401082 s.version += 1
441 } else if s.built[index] == nil {
1083 // The full chunk supersedes any rescue slice under it.
1084 if s.partial.removeValue(forKey: index) != nil {
1085 let rURL = self.rescueURL(key: media.cacheKey, index: index)
1086 DispatchQueue.global(qos: .utility).async {
1087 let sz = (try? FileManager.default.attributesOfItem(
1088 atPath: rURL.path)[.size] as? NSNumber)?.int64Value ?? 0
1089 try? FileManager.default.removeItem(at: rURL)
1090 if sz > 0 {
1091 DispatchQueue.main.async {
1092 MediaPipeline.shared.noteBytesAdded(-sz)
1093 }
1094 }
1095 }
1096 }
1097 } else if s.built[index] == nil, slice == nil {
4421098 // Only a hard failure when we have NOTHING; a failed
4431099 // upgrade just keeps the existing lower-quality chunk.
1100 // (A failed rescue is not a failure — the full build
1101 // is still queued and gets its own attempt.)
4441102 s.failed.insert(index)
4451103 }
4461104 self.states[media.cacheKey] = s
447 if r.ok {
1105 if r.ok, slice == nil {
4481106 self.persistWidths(key: media.cacheKey)
4491107 // Only realtime (urgent) builds inform the realtime
4501108 // controller — a slow, quality-first background build
......@@ -462,9 +1120,19 @@ final class ChunkManager {
4621120 }
4631121 }
4641122
465 struct BuildResult { var ok: Bool; var wall: Double; var dur: Double; var isNetwork: Bool }
1123 struct BuildResult {
1124 var ok: Bool; var wall: Double; var dur: Double; var isNetwork: Bool
1125 /// Bytes of the finished chunk file / of the file it replaced (an
1126 /// upgrade re-encode) — the ledger records the difference.
1127 var newBytes: Int64 = 0; var oldBytes: Int64 = 0
1128 }
1129
1130 /// Seconds of content a rescue slice encodes — enough to watch while the
1131 /// full chunk builds behind it, small enough to land in a few seconds.
1132 private static let rescueSeconds = 10.0
4661133
467 private func buildChunk(media: MediaItem, index: Int, width: Int, fps: Int) -> BuildResult {
1134 private func buildChunk(media: MediaItem, index: Int, width: Int, fps: Int,
1135 slice: (offset: Double, dur: Double)? = nil) -> BuildResult {
4681136 let isNet = Self.isNetworkPath(media.path)
4691137 func fail(_ wall: Double = 0, _ dur: Double = 0) -> BuildResult {
4701138 BuildResult(ok: false, wall: wall, dur: dur, isNetwork: isNet)
......@@ -472,11 +1140,14 @@ final class ChunkManager {
4721140 guard let ffmpeg = MediaPipeline.findExecutable("ffmpeg") else { return fail() }
4731141 let dir = chunksDir(media.cacheKey)
4741142 try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
475 let final = chunkURL(key: media.cacheKey, index: index)
476 let tmp = dir.appendingPathComponent(String(format: ".c%06d.partial.mov", index))
1143 let final = slice == nil ? chunkURL(key: media.cacheKey, index: index)
1144 : rescueURL(key: media.cacheKey, index: index)
1145 let tmp = dir.appendingPathComponent(String(
1146 format: slice == nil ? ".c%06d.partial.mov" : ".r%06d.partial.mov", index))
4771147 try? FileManager.default.removeItem(at: tmp)
478 let start = Double(index) * Self.chunkSeconds
479 let dur = min(Self.chunkSeconds, media.duration - start)
1148 let start = Double(index) * Self.chunkSeconds + (slice?.offset ?? 0)
1149 var dur = min(Self.chunkSeconds - (slice?.offset ?? 0), media.duration - start)
1150 if let slice { dur = min(dur, slice.dur) }
4801151 guard dur > 0.01 else { return fail() }
4811152
4821153 func args(encoder: String) -> [String] {
......@@ -485,7 +1156,12 @@ final class ChunkManager {
4851156 "-i", media.path,
4861157 "-t", String(format: "%.3f", dur),
4871158 "-map", "0:v:0",
488 "-vf", "scale='min(\(width),iw)':-2,fps=\(fps)",
1159 // Lanczos downscale: swscale's default (bicubic) softens the
1160 // hard edges of up-scaled/pixel-art content into an annoying
1161 // blur; lanczos keeps the downscaled proxy crisp (not
1162 // nearest-neighbour "pixely", just sharp) — which matters more
1163 // than resolution for editing legibility.
1164 "-vf", "scale=w='min(\(width),iw)':h=-2:flags=lanczos,fps=\(fps)",
4891165 "-c:v", encoder, "-profile:v", "proxy"]
4901166 if media.hasAudio { a += ["-map", "0:a:0", "-c:a", "pcm_s16le"] }
4911167 a.append(tmp.path)
......@@ -498,10 +1174,16 @@ final class ChunkManager {
4981174 }
4991175 let wall = Date().timeIntervalSince(t0)
5001176 if res.exitCode == 0 {
1177 func size(_ url: URL) -> Int64 {
1178 (try? FileManager.default.attributesOfItem(atPath: url.path)[.size]
1179 as? NSNumber)?.int64Value ?? 0
1180 }
1181 let newBytes = size(tmp), oldBytes = size(final)
5011182 try? FileManager.default.removeItem(at: final)
5021183 do { try FileManager.default.moveItem(at: tmp, to: final) }
5031184 catch { return fail(wall, dur) }
504 return BuildResult(ok: true, wall: wall, dur: dur, isNetwork: isNet)
1185 return BuildResult(ok: true, wall: wall, dur: dur, isNetwork: isNet,
1186 newBytes: newBytes, oldBytes: oldBytes)
5051187 }
5061188 try? FileManager.default.removeItem(at: tmp)
5071189 return fail(wall, dur)
......@@ -611,16 +1293,30 @@ final class ChunkManager {
6111293 return (AVMutableComposition(), -2)
6121294 }
6131295
1296 /// One stitched piece of a media's composition: a full chunk (offset 0,
1297 /// dur nil) or a rescue slice sitting `offset` seconds into its grid slot.
1298 private struct CompPart {
1299 let url: URL
1300 let offset: Double
1301 let dur: Double?
1302 }
1303
6141304 private func kickCompositionBuild(media: MediaItem) {
6151305 let key = media.cacheKey
6161306 guard !compBuilding.contains(key) else { return }
6171307 compBuilding.insert(key)
6181308 let s = state(for: media)
6191309 let version = s.version
620 let chunkURLs = Dictionary(uniqueKeysWithValues:
621 s.built.keys.map { ($0, chunkURL(key: key, index: $0)) })
1310 var parts: [Int: CompPart] = [:]
1311 for i in s.built.keys {
1312 parts[i] = CompPart(url: chunkURL(key: key, index: i), offset: 0, dur: nil)
1313 }
1314 for (i, p) in s.partial where parts[i] == nil {
1315 parts[i] = CompPart(url: rescueURL(key: key, index: i),
1316 offset: p.offset, dur: p.dur)
1317 }
6221318 Task.detached(priority: .userInitiated) {
623 let (comp, playable) = await Self.assemble(media: media, chunkURLs: chunkURLs)
1319 let (comp, playable) = await Self.assemble(media: media, parts: parts)
6241320 await MainActor.run { [self] in
6251321 self.compBuilding.remove(key)
6261322 var s = self.states[key] ?? MediaState()
......@@ -634,8 +1330,24 @@ final class ChunkManager {
6341330 }
6351331 }
6361332
1333 /// A chunk asset's tracks, loaded and ready to insert. `asset` is what
1334 /// keeps the tracks alive: an AVAssetTrack does NOT retain its asset, and
1335 /// inserting a track whose asset has been deallocated fails with
1336 /// -11800/-12780 — silently under `try?`, leaving a black GAP in the
1337 /// composition for a chunk that's perfectly healthy on disk. (Bit us for
1338 /// real: the task-group refactor returned bare tracks, and whether a slot
1339 /// went black depended on autorelease timing.)
1340 private struct LoadedPart {
1341 let index: Int
1342 let asset: AVURLAsset
1343 let v: AVAssetTrack
1344 let a: AVAssetTrack?
1345 let duration: CMTime
1346 let offset: Double
1347 }
1348
6371349 private static func assemble(media: MediaItem,
638 chunkURLs: [Int: URL]) async -> (AVComposition, Bool) {
1350 parts: [Int: CompPart]) async -> (AVComposition, Bool) {
6391351 let comp = AVMutableComposition()
6401352 guard let vTrack = comp.addMutableTrack(
6411353 withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
......@@ -647,6 +1359,51 @@ final class ChunkManager {
6471359 let origV = try? await original.loadTracks(withMediaType: .video).first
6481360 let origA = try? await original.loadTracks(withMediaType: .audio).first
6491361
1362 // Load every chunk asset's tracks CONCURRENTLY (bounded), then insert
1363 // in order. The old one-await-per-chunk loop made a media with
1364 // hundreds of built chunks take seconds to reassemble — and a
1365 // reassembly runs every time a chunk lands, right when the user is
1366 // waiting to see it.
1367 let wantAudio = aTrack != nil
1368 var loaded: [Int: LoadedPart] = [:]
1369 await withTaskGroup(of: LoadedPart?.self) { group in
1370 var pending = Array(parts).sorted { $0.key < $1.key }[...]
1371 var inFlight = 0
1372 func addNext() {
1373 guard let (i, part) = pending.first else { return }
1374 pending = pending.dropFirst()
1375 inFlight += 1
1376 group.addTask {
1377 let chunk = AVURLAsset(url: part.url)
1378 guard let v = try? await chunk.loadTracks(withMediaType: .video).first
1379 else { return nil }
1380 let d = (try? await chunk.load(.duration)) ?? .zero
1381 let a = wantAudio
1382 ? try? await chunk.loadTracks(withMediaType: .audio).first : nil
1383 return LoadedPart(index: i, asset: chunk, v: v, a: a, duration: d,
1384 offset: part.offset)
1385 }
1386 }
1387 for _ in 0..<8 { addNext() }
1388 while inFlight > 0 {
1389 guard let r = await group.next() else { break }
1390 inFlight -= 1
1391 if let r { loaded[r.index] = r }
1392 addNext()
1393 }
1394 }
1395
1396 func fillFromOriginal(_ range: CMTimeRange) {
1397 if let origV {
1398 try? vTrack.insertTimeRange(range, of: origV, at: range.start)
1399 if let aTrack, let origA {
1400 try? aTrack.insertTimeRange(range, of: origA, at: range.start)
1401 }
1402 } else {
1403 vTrack.insertEmptyTimeRange(range)
1404 }
1405 }
1406
6501407 let n = Self.chunkCount(duration: media.duration)
6511408 for i in 0..<n {
6521409 let startSec = Double(i) * Self.chunkSeconds
......@@ -654,29 +1411,36 @@ final class ChunkManager {
6541411 guard durSec > 0.001 else { break }
6551412 let at = CMTime(seconds: startSec, preferredTimescale: 600)
6561413 let dur = CMTime(seconds: durSec, preferredTimescale: 600)
657 var inserted = false
658 if let url = chunkURLs[i] {
659 let chunk = AVURLAsset(url: url)
660 if let v = try? await chunk.loadTracks(withMediaType: .video).first {
661 let chunkDuration = (try? await chunk.load(.duration)) ?? .zero
662 let r = CMTimeRange(start: .zero, duration: min(dur, chunkDuration))
663 try? vTrack.insertTimeRange(r, of: v, at: at)
664 if let aTrack, let a = try? await chunk.loadTracks(withMediaType: .audio).first {
665 try? aTrack.insertTimeRange(r, of: a, at: at)
666 }
667 inserted = true
668 }
1414 guard let part = loaded[i] else {
1415 fillFromOriginal(CMTimeRange(start: at, duration: dur))
1416 continue
6691417 }
670 if !inserted {
671 if let origV {
672 let r = CMTimeRange(start: at, duration: dur)
673 try? vTrack.insertTimeRange(r, of: origV, at: at)
674 if let aTrack, let origA {
675 try? aTrack.insertTimeRange(r, of: origA, at: at)
676 }
677 } else {
678 vTrack.insertEmptyTimeRange(CMTimeRange(start: at, duration: dur))
679 }
1418 let sliceAt = CMTime(seconds: startSec + part.offset, preferredTimescale: 600)
1419 let sliceDur = min(part.duration,
1420 CMTime(seconds: durSec - part.offset, preferredTimescale: 600))
1421 // Rescue slice: original (or empty) leads in, the slice covers the
1422 // playhead's neighborhood, original (or empty) fills the tail.
1423 if part.offset > 0.001 {
1424 fillFromOriginal(CMTimeRange(start: at, end: sliceAt))
1425 }
1426 let r = CMTimeRange(start: .zero, duration: sliceDur)
1427 do {
1428 try vTrack.insertTimeRange(r, of: part.v, at: sliceAt)
1429 } catch {
1430 // A healthy chunk that fails to stitch plays back as a BLACK
1431 // gap — never let that be silent again (a dropped asset
1432 // reference made every insert fail exactly this way once).
1433 SeqLog.log("[cache] comp insert FAILED %@#%d dur=%.2f: %@",
1434 String(media.cacheKey.prefix(8)), i, sliceDur.seconds,
1435 String(describing: error))
1436 }
1437 if let aTrack, let a = part.a {
1438 try? aTrack.insertTimeRange(r, of: a, at: sliceAt)
1439 }
1440 let sliceEnd = sliceAt + sliceDur
1441 let slotEnd = at + dur
1442 if sliceEnd + CMTime(seconds: 0.001, preferredTimescale: 600) < slotEnd {
1443 fillFromOriginal(CMTimeRange(start: sliceEnd, end: slotEnd))
6801444 }
6811445 }
6821446 return (comp, origV != nil)
sequencer/Sources/Sequencer/Document.swift+31-1
......@@ -12,7 +12,7 @@ import AppKit
1212/// and force the document type for any `.sq` URL — regardless of what
1313/// LaunchServices believes — so opening always resolves to `ProjectDocument`.
1414final class ProjectDocumentController: NSDocumentController {
15 private static let projectType = "com.clover.sequencer.project"
15 private static let projectType = "net.paperclover.sequencer.project"
1616 private let sqPanelDelegate = SQOpenPanelDelegate()
1717
1818 /// Pin the document type for `.sq` URLs so it never resolves to a folder,
......@@ -41,6 +41,21 @@ final class ProjectDocumentController: NSDocumentController {
4141 }
4242 }
4343 }
44
45 /// Opening a project from a pristine untitled window replaces that window
46 /// instead of leaving an empty one behind.
47 override func openDocument(withContentsOf url: URL, display displayDocument: Bool,
48 completionHandler: @escaping (NSDocument?, Bool, Error?) -> Void) {
49 let blanks = documents.compactMap { $0 as? ProjectDocument }.filter(\.isPristineUntitled)
50 super.openDocument(withContentsOf: url, display: displayDocument) { doc, alreadyOpen, error in
51 if doc != nil, error == nil {
52 for blank in blanks where blank.isPristineUntitled && blank !== doc {
53 blank.close()
54 }
55 }
56 completionHandler(doc, alreadyOpen, error)
57 }
58 }
4459}
4560
4661/// Enables only `.sq` items (package directories or legacy flat files) in the
......@@ -73,6 +88,21 @@ final class ProjectDocument: NSDocument {
7388 /// first explicit save.
7489 override class var autosavesInPlace: Bool { true }
7590
91 /// Never saved, never edited, and nothing on the timeline — safe to close
92 /// when a real project opens over it.
93 var isPristineUntitled: Bool {
94 fileURL == nil && !isDocumentEdited
95 && ctx.store.project.media.isEmpty && ctx.store.project.clips.isEmpty
96 }
97
98 /// Stop per-document services before AppKit tears the document down, so a
99 /// proxy build or the playback clock finishing after close can't touch the
100 /// now-dangling `unowned` context.
101 override func close() {
102 ctx.shutdown()
103 super.close()
104 }
105
76106 override func makeWindowControllers() {
77107 let wc = SequencerWindowController(ctx: ctx)
78108 addWindowController(wc)
sequencer/Sources/Sequencer/DocumentContext.swift+40-3
......@@ -47,6 +47,25 @@ final class DocumentContext {
4747 }
4848 }
4949
50 private var didShutdown = false
51
52 /// Tear the document's services down while `self` is still alive — called
53 /// from `ProjectDocument.close()`. Services hold `unowned var ctx`; an
54 /// in-flight chunk build or the 60 Hz clock landing a callback AFTER the
55 /// context deallocs would trap on that dangling reference. Stopping them here
56 /// (before dealloc) makes every such late callback a no-op.
57 func shutdown() {
58 guard !didShutdown else { return }
59 didShutdown = true
60 playback.stop()
61 chunks.stop()
62 comps.stopWatching()
63 if let reconcileObserver {
64 NotificationCenter.default.removeObserver(reconcileObserver)
65 self.reconcileObserver = nil
66 }
67 }
68
5069 deinit {
5170 if let reconcileObserver { NotificationCenter.default.removeObserver(reconcileObserver) }
5271 comps.stopWatching()
......@@ -55,8 +74,22 @@ final class DocumentContext {
5574 /// Start the per-document services (playback clock, comps folder watch,
5675 /// derived-asset warmup). Called once by the window controller after load.
5776 func startServices() {
77 // Trim the shared media cache under its byte cap now that this project's
78 // media is registered (so its own cache is protected). Opening a
79 // document is also the natural moment to re-measure the cache from
80 // disk (reconcile) so the incremental ledger can't drift for long.
81 MediaPipeline.shared.evictIfNeeded(reconcile: true)
5882 MediaPipeline.shared.ensureDerivedAssets(for: store.project)
59 chunks.ensure(for: store.project)
83 // NOTE: the whole-project proxy pre-build (`chunks.ensure`) is NOT kicked
84 // here. macOS state restoration reopens *every* previously-open project on
85 // launch, and each one running `ensure` would transcode its full ProRes
86 // proxy set in parallel — every open project's media counts as "in use", so
87 // eviction can't trim any of it and the shared cache blows past its cap and
88 // fills the disk. Instead the fill is triggered when a document's window
89 // becomes main (SequencerWindowController.windowDidBecomeMain): the
90 // frontmost project fills immediately, a background/restored project fills
91 // only once you switch to it. On-demand playhead builds (`want`) still run
92 // for whatever is actually playing.
6093 comps.rescan()
6194 comps.startWatching()
6295 playback.start()
......@@ -69,10 +102,14 @@ final class DocumentContext {
69102 /// (Settings, Export, cache eviction) that operate on whichever project is
70103 /// frontmost. Falls back to the headless context when nothing is open.
71104 static var current: DocumentContext {
72 if let wc = NSApp.keyWindow?.windowController as? SequencerWindowController {
105 // `NSApp` is nil in the headless `--selftest` harness (no NSApplication);
106 // guard it so callers on the build path can ask "am I frontmost?" without
107 // crashing — with no app, the headless context is by definition current.
108 guard let app = NSApp else { return headless }
109 if let wc = app.keyWindow?.windowController as? SequencerWindowController {
73110 return wc.ctx
74111 }
75 if let wc = NSApp.mainWindow?.windowController as? SequencerWindowController {
112 if let wc = app.mainWindow?.windowController as? SequencerWindowController {
76113 return wc.ctx
77114 }
78115 if let doc = NSDocumentController.shared.currentDocument as? ProjectDocument {
sequencer/Sources/Sequencer/Export.swift+30-4
......@@ -186,6 +186,7 @@ enum ExportError: Error, LocalizedError {
186186 case fusion(String)
187187 case intermediateFailed
188188 case encodeFailed(String)
189 case missingMedia([String])
189190
190191 var errorDescription: String? {
191192 switch self {
......@@ -194,6 +195,11 @@ enum ExportError: Error, LocalizedError {
194195 case .fusion(let s): return s
195196 case .intermediateFailed: return "Could not render the timeline composition."
196197 case .encodeFailed(let s): return "ffmpeg failed to encode the output.\n\n\(s)"
198 case .missingMedia(let names):
199 let list = names.map { " • \($0)" }.joined(separator: "\n")
200 return "Export was stopped because this media could not be read — the "
201 + "output would silently drop those clips. Reconnect the drive or "
202 + "relink the files and try again:\n\n\(list)"
197203 }
198204 }
199205}
......@@ -244,24 +250,34 @@ enum Exporter {
244250 let comp = AVMutableComposition()
245251 let wantVideo = job.format.isVideo && !segments.isEmpty
246252
253 // Media that couldn't be read while building the composition. A silent
254 // gap here means the render quietly drops content, so we collect every
255 // offending source and abort (below) rather than hand ffmpeg a truncated
256 // intermediate that looks like a successful export.
257 var missing = Set<String>()
258
247259 // Video: one track, segments appended left-to-right with empty gaps.
248260 if wantVideo, let vTrack = comp.addMutableTrack(
249261 withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) {
250262 var cursor = 0.0
251263 for seg in segments {
252 guard let media = project.media(seg.mediaId) else { continue }
264 guard let media = project.media(seg.mediaId) else {
265 missing.insert("(missing media reference)"); continue
266 }
253267 if seg.start > cursor + 1e-6 {
254268 vTrack.insertEmptyTimeRange(cmRange(cursor, seg.start - cursor))
255269 cursor = seg.start
256270 }
257271 let asset = AVURLAsset(url: media.url)
258272 guard let src = loadTracksSync(asset, mediaType: .video).first else {
273 missing.insert(media.displayName)
259274 vTrack.insertEmptyTimeRange(cmRange(cursor, seg.duration)); cursor += seg.duration; continue
260275 }
261276 let srcDur = seg.duration * seg.speed
262277 let range = cmRange(seg.srcIn, srcDur)
263278 let at = cm(cursor)
264 try? vTrack.insertTimeRange(range, of: src, at: at)
279 do { try vTrack.insertTimeRange(range, of: src, at: at) }
280 catch { missing.insert(media.displayName) }
265281 if abs(seg.speed - 1) > 1e-6 {
266282 // Nothing has been appended after `at` yet, so scaling this
267283 // range back to timeline duration is safe.
......@@ -280,9 +296,12 @@ enum Exporter {
280296 withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
281297 else { continue }
282298 let asset = AVURLAsset(url: media.url)
283 guard let src = loadTracksSync(asset, mediaType: .audio).first else { continue }
299 guard let src = loadTracksSync(asset, mediaType: .audio).first else {
300 missing.insert(media.displayName); continue
301 }
284302 let srcDur = clip.duration * clip.speed
285 try? aTrack.insertTimeRange(cmRange(clip.srcIn, srcDur), of: src, at: cm(clip.start))
303 do { try aTrack.insertTimeRange(cmRange(clip.srcIn, srcDur), of: src, at: cm(clip.start)) }
304 catch { missing.insert(media.displayName) }
286305 if abs(clip.speed - 1) > 1e-6 {
287306 aTrack.scaleTimeRange(cmRange(clip.start, srcDur), toDuration: cm(clip.duration))
288307 }
......@@ -298,6 +317,13 @@ enum Exporter {
298317 mixParams.append(p)
299318 }
300319
320 // Any unreadable source means the render would be silently truncated —
321 // stop and tell the user exactly which files, rather than produce a
322 // partial export that looks complete.
323 if !missing.isEmpty {
324 throw ExportError.missingMedia(missing.sorted())
325 }
326
301327 // Render the composition to an intermediate the encoder can read.
302328 let tmp = tempDir()
303329 defer { try? FileManager.default.removeItem(at: tmp) }
sequencer/Sources/Sequencer/FrameCache.swift created+106
......@@ -0,0 +1,106 @@
1import Foundation
2import AVFoundation
3import QuartzCore
4
5/// RAM cache of decoded, full-quality stand-in frames.
6///
7/// The players keep only ~1s of decoded video buffered, so the instant of a
8/// clip boundary (an item swap, or a long seek inside a stitched composition)
9/// has nothing exact to show for a beat — the viewer fell back to a 240px
10/// filmstrip thumb, or the "Loading Media…" spinner when even that missed.
11/// This cache holds the *exact* frames those moments need: cut heads and
12/// tails near the playhead are warmed before the boundary arrives, and any
13/// frame the viewer is currently unable to show gets decoded on demand from
14/// the sharpest thing on disk (chunk, rescue slice, or playable original).
15///
16/// Budget comes from the `maxRAMGB` default (Settings → Global, 2 GB when
17/// unset); a 1080p frame is ~8 MB, so even the default holds a couple of
18/// hundred boundaries. Frames are keyed on 0.25s buckets — a stand-in a
19/// fraction of a second off is indistinguishable during the sub-second hold
20/// it covers.
21final class FrameCache {
22 static let shared = FrameCache()
23 private static let bucket = 0.25
24
25 static var ramGB: Int {
26 let gb = UserDefaults.standard.integer(forKey: "maxRAMGB")
27 return gb > 0 ? gb : 2
28 }
29
30 private let cache = NSCache<NSString, CGImage>()
31 /// Decodes in flight / recently failed (source vanished mid-decode, or a
32 /// truncated file) — failures back off so a hopeless frame isn't retried
33 /// every tick. Main-thread only.
34 private var inFlight = Set<String>()
35 private var failedAt: [String: Double] = [:]
36 private let genQueue = DispatchQueue(label: "sequencer.framecache",
37 qos: .userInitiated)
38
39 init() { refreshBudget() }
40
41 /// Re-read the budget after the Settings field changes.
42 func refreshBudget() {
43 cache.totalCostLimit = Self.ramGB * 1_000_000_000
44 }
45
46 private func key(_ mediaKey: String, bucket: Int) -> NSString {
47 "\(mediaKey)@\(bucket)" as NSString
48 }
49 private func bucketIndex(_ src: Double) -> Int {
50 Int((src / Self.bucket).rounded())
51 }
52
53 /// The cached frame nearest `src`, if any (this bucket or a neighbor).
54 func image(media: MediaItem, at src: Double) -> CGImage? {
55 let b = bucketIndex(src)
56 for d in [0, -1, 1] {
57 if let img = cache.object(forKey: key(media.cacheKey, bucket: b + d)) {
58 return img
59 }
60 }
61 return nil
62 }
63
64 /// Decode the frame at `src` into the cache if it isn't there already.
65 /// `source` is a playable (file, time-in-file) pair for this moment,
66 /// resolved on the main thread by `ChunkManager.frameSource` — nil (no
67 /// chunk, no playable original) is a silent no-op. Posts
68 /// `.viewerNeedsRefresh` when the frame lands so a waiting cell picks it
69 /// up. Cheap and self-deduping; safe to call every tick.
70 /// `SEQ_NOWARM=1` disables decodes (diagnostic kill switch).
71 private static let disabled = ProcessInfo.processInfo.environment["SEQ_NOWARM"] != nil
72
73 func warm(mediaKey: String, at src: Double, source: (url: URL, time: Double)?) {
74 guard !Self.disabled, let source else { return }
75 let k = key(mediaKey, bucket: bucketIndex(src))
76 let ks = k as String
77 guard cache.object(forKey: k) == nil, !inFlight.contains(ks) else { return }
78 let now = CACurrentMediaTime()
79 if let failed = failedAt[ks], now - failed < 5 { return }
80 inFlight.insert(ks)
81 genQueue.async { [self] in
82 let asset = AVURLAsset(url: source.url)
83 let gen = AVAssetImageGenerator(asset: asset)
84 gen.appliesPreferredTrackTransform = true
85 // Exact going in, a hair of slack after: the frame ON the cut is
86 // what a boundary needs, but a keyframe-snap decode is far cheaper
87 // than a precise reverse walk.
88 gen.requestedTimeToleranceBefore = .zero
89 gen.requestedTimeToleranceAfter = CMTime(seconds: 0.2, preferredTimescale: 600)
90 let t = CMTime(seconds: max(0, source.time), preferredTimescale: 60000)
91 let img = try? gen.copyCGImage(at: t, actualTime: nil)
92 DispatchQueue.main.async {
93 self.inFlight.remove(ks)
94 if let img {
95 self.failedAt.removeValue(forKey: ks)
96 self.cache.setObject(img, forKey: k,
97 cost: img.bytesPerRow * img.height)
98 NotificationCenter.default.post(name: .viewerNeedsRefresh,
99 object: nil)
100 } else {
101 self.failedAt[ks] = CACurrentMediaTime()
102 }
103 }
104 }
105 }
106}
sequencer/Sources/Sequencer/HangMonitor.swift created+190
......@@ -0,0 +1,190 @@
1import Foundation
2import Darwin
3
4/// Main-thread hang watchdog. A background thread pings the main queue every
5/// 20ms; when the pong stops coming back for >100ms, the main thread is
6/// stalled — exactly the "microhang" that makes playback video stutter while
7/// audio (decoded off-main by CoreAudio) keeps going. While the stall lasts,
8/// the watchdog suspends the main thread for microseconds at a time, walks its
9/// frame pointers, and symbolicates — so the log names the culprit, not just
10/// the duration. Lines go to ~/Library/Logs/Sequencer.log via SeqLog:
11///
12/// [hang] main thread 0.34s during playback (rate 1.0) — 3 samples,
13/// top: ViewerGridView.update() ← CA::Transaction::commit ← …
14///
15/// Cost when healthy: one trivial main-queue block per 20ms. Disable with
16/// SEQ_NOHANGWATCH=1.
17/// SEQ_HANGTEST target: a recognizable frame that should appear in the
18/// sampled stack. Spins (not sleeps) so the pc sits in our own code.
19@inline(never)
20func hangTestStall() {
21 let until = CFAbsoluteTimeGetCurrent() + 0.4
22 var sink = 0.0
23 while CFAbsoluteTimeGetCurrent() < until { sink += sin(sink) + 1 }
24 _ = sink
25}
26
27enum HangMonitor {
28 /// Written from PlaybackController.setRate (main), read by the watchdog
29 /// thread. Benign race — it only annotates log lines.
30 nonisolated(unsafe) static var playbackRate: Double = 0
31
32 private nonisolated(unsafe) static var mainThread: thread_t = 0
33 private nonisolated(unsafe) static var lastPong = CFAbsoluteTimeGetCurrent()
34 private nonisolated(unsafe) static var pingInFlight = false
35 private static let lock = NSLock()
36 private static let threshold = 0.1 // report stalls longer than this
37
38 /// Call once from the main thread at startup.
39 static func start() {
40 guard ProcessInfo.processInfo.environment["SEQ_NOHANGWATCH"] == nil else { return }
41 mainThread = pthread_mach_thread_np(pthread_self())
42 let t = Thread { watch() }
43 t.name = "sequencer.hangwatch"
44 t.qualityOfService = .userInitiated
45 t.start()
46 }
47
48 private static func watch() {
49 while true {
50 usleep(20_000)
51 lock.lock()
52 let age = CFAbsoluteTimeGetCurrent() - lastPong
53 let busy = pingInFlight
54 if !busy {
55 pingInFlight = true
56 lock.unlock()
57 DispatchQueue.main.async {
58 lock.lock()
59 lastPong = CFAbsoluteTimeGetCurrent()
60 pingInFlight = false
61 lock.unlock()
62 }
63 } else {
64 lock.unlock()
65 }
66 if busy, age > threshold { observeStall(begunAge: age) }
67 }
68 }
69
70 /// Main thread has been unresponsive for `begunAge` already. Sample its
71 /// stack periodically until it recovers, then log one line.
72 private static func observeStall(begunAge: Double) {
73 let start = CFAbsoluteTimeGetCurrent() - begunAge
74 var samples: [[String]] = []
75 while true {
76 if samples.count < 5 {
77 let frames = sampleMainStack()
78 if !frames.isEmpty { samples.append(frames) }
79 }
80 usleep(100_000)
81 lock.lock()
82 let stillStalled = pingInFlight && lastPong < start
83 lock.unlock()
84 if !stillStalled { break }
85 }
86 // Wait for the pong to actually land so the duration is honest.
87 var duration = CFAbsoluteTimeGetCurrent() - start
88 for _ in 0..<200 { // give the queued pong up to 2s to run
89 lock.lock(); let pong = lastPong; lock.unlock()
90 if pong >= start { duration = pong - start; break }
91 usleep(10_000)
92 }
93 guard duration > threshold else { return }
94 let rate = playbackRate
95 let during = rate != 0 ? String(format: " during playback (rate %.1f)", rate) : ""
96 let top = samples.first?.prefix(8).joined(separator: " ← ") ?? "no stack (sampling failed)"
97 SeqLog.log("[hang] main thread %.2fs%@ — %d sample%@, top: %@",
98 duration, during, samples.count, samples.count == 1 ? "" : "s", top)
99 for extra in samples.dropFirst() where extra.first != samples.first?.first {
100 SeqLog.log("[hang] also seen: %@", extra.prefix(5).joined(separator: " ← "))
101 }
102 }
103
104 // MARK: stack sampling (arm64 frame-pointer walk)
105
106 /// Fixed buffers, touched only by the watchdog thread. They exist so the
107 /// suspend window below performs ZERO allocations: if the main thread is
108 /// suspended while holding the malloc lock, any malloc here deadlocks the
109 /// whole app (watchdog waits on the lock, suspended main can never release
110 /// it, thread_resume never runs). This happened — main frozen mid free()
111 /// in drawRuler, watchdog frozen in Array.append → permanent freeze.
112 private static let maxFrames = 50
113 private nonisolated(unsafe) static var pcBuf = [UInt64](repeating: 0, count: maxFrames)
114 private nonisolated(unsafe) static var pairBuf = [UInt64](repeating: 0, count: 2)
115
116 private static func sampleMainStack() -> [String] {
117 guard mainThread != 0, thread_suspend(mainThread) == KERN_SUCCESS else { return [] }
118 // ---- suspend window: no allocation, no locks, no ObjC/Swift runtime
119 // calls that might take either. Only mach syscalls and raw stores. ----
120 var n = 0
121 var state = arm_thread_state64_t()
122 var count = mach_msg_type_number_t(MemoryLayout<arm_thread_state64_t>.size
123 / MemoryLayout<natural_t>.size)
124 let kr = withUnsafeMutablePointer(to: &state) { ptr in
125 ptr.withMemoryRebound(to: natural_t.self, capacity: Int(count)) {
126 thread_get_state(mainThread, ARM_THREAD_STATE64, $0, &count)
127 }
128 }
129 if kr == KERN_SUCCESS {
130 pcBuf[n] = arm64PC(state); n += 1
131 let lr = arm64LR(state)
132 var fp = arm64FP(state)
133 if lr != 0 { pcBuf[n] = lr; n += 1 }
134 // Frame layout: [fp] = caller fp, [fp+8] = return address.
135 while n < maxFrames {
136 guard fp != 0, fp & 0x7 == 0 else { break }
137 var outSize: mach_vm_size_t = 16
138 let r = pairBuf.withUnsafeMutableBytes { buf in
139 mach_vm_read_overwrite(mach_task_self_, mach_vm_address_t(fp), 16,
140 mach_vm_address_t(UInt(bitPattern: buf.baseAddress)),
141 &outSize)
142 }
143 guard r == KERN_SUCCESS, pairBuf[1] != 0 else { break }
144 pcBuf[n] = pairBuf[1]; n += 1
145 guard pairBuf[0] > fp else { break } // stacks grow down; fp chain grows up
146 fp = pairBuf[0]
147 }
148 }
149 thread_resume(mainThread)
150 // ---- end suspend window; symbolication may allocate freely. ----
151 return (0..<n).compactMap { symbolicate(pcBuf[$0]) }
152 }
153
154 private static func arm64PC(_ s: arm_thread_state64_t) -> UInt64 { s.__pc }
155 private static func arm64LR(_ s: arm_thread_state64_t) -> UInt64 {
156 s.__lr & 0x0000_7FFF_FFFF_FFFF // strip ptrauth bits
157 }
158 private static func arm64FP(_ s: arm_thread_state64_t) -> UInt64 { s.__fp }
159
160 private typealias DemangleFn = @convention(c) (
161 UnsafePointer<CChar>?, Int, UnsafeMutablePointer<CChar>?,
162 UnsafeMutablePointer<Int>?, UInt32) -> UnsafeMutablePointer<CChar>?
163 private static let demangleFn: DemangleFn? = {
164 guard let sym = dlsym(dlopen(nil, RTLD_NOW), "swift_demangle") else { return nil }
165 return unsafeBitCast(sym, to: DemangleFn.self)
166 }()
167
168 private static func symbolicate(_ pc: UInt64) -> String? {
169 let stripped = pc & 0x0000_7FFF_FFFF_FFFF
170 var info = Dl_info()
171 guard dladdr(UnsafeRawPointer(bitPattern: UInt(stripped)), &info) != 0 else { return nil }
172 var name: String
173 if let sname = info.dli_sname {
174 name = String(cString: sname)
175 if name.hasPrefix("$s") || name.hasPrefix("_$s"), let fn = demangleFn,
176 let d = fn(name, name.utf8.count, nil, nil, 0) {
177 name = String(cString: d)
178 free(d)
179 // Demangled Swift names are long; keep the signature-free head.
180 if let paren = name.firstIndex(of: "(") { name = String(name[..<paren]) + "()" }
181 }
182 } else if let fname = info.dli_fname {
183 name = (String(cString: fname) as NSString).lastPathComponent
184 + String(format: "+0x%llx", stripped - UInt64(UInt(bitPattern: info.dli_fbase)))
185 } else {
186 return nil
187 }
188 return name
189 }
190}
sequencer/Sources/Sequencer/LayerTest.swift created+77
......@@ -0,0 +1,77 @@
1import AppKit
2import AVFoundation
3
4/// Diagnostic (temporary): `sequencer --layertest <key:orig> [<key:orig> …]`.
5/// Builds the same chunk+original stitched compositions the app plays and
6/// shows each in a bare AVPlayerLayer tile — isolating "do N of these render
7/// concurrently" from every other moving part of the app. Seeks all tiles to
8/// SEQ_LAYERTEST_T (default 16.6).
9@MainActor
10func runLayerTest(specs: [String]) {
11 let t = Double(ProcessInfo.processInfo.environment["SEQ_LAYERTEST_T"] ?? "") ?? 16.6
12 let cols = specs.count
13 let tileW = 420.0, tileH = 260.0
14 let win = NSWindow(contentRect: NSRect(x: 100, y: 300,
15 width: tileW * Double(cols), height: tileH),
16 styleMask: [.titled], backing: .buffered, defer: false)
17 win.title = "LayerTest t=\(t)"
18 win.contentView!.wantsLayer = true
19 win.makeKeyAndOrderFront(nil)
20 NSApp.activate(ignoringOtherApps: true)
21
22 for (col, spec) in specs.enumerated() {
23 let bits = spec.split(separator: ":", maxSplits: 1).map(String.init)
24 guard bits.count == 2 else { continue }
25 let key = bits[0], orig = bits[1]
26 let player = AVPlayer()
27 player.automaticallyWaitsToMinimizeStalling = false
28 let layer = AVPlayerLayer(player: player)
29 layer.frame = NSRect(x: Double(col) * tileW, y: 0, width: tileW, height: tileH)
30 layer.videoGravity = .resizeAspect
31 layer.backgroundColor = NSColor.purple.cgColor // un-rendered = purple
32 win.contentView!.layer!.addSublayer(layer)
33 let chunkDir = MediaPipeline.shared.cacheRoot
34 .appendingPathComponent(key).appendingPathComponent("chunks")
35 Task { @MainActor in
36 let comp = AVMutableComposition()
37 let vTrack = comp.addMutableTrack(withMediaType: .video,
38 preferredTrackID: kCMPersistentTrackID_Invalid)!
39 let original = AVURLAsset(url: URL(fileURLWithPath: orig))
40 let origV = try? await original.loadTracks(withMediaType: .video).first
41 let origDur = (try? await original.load(.duration))?.seconds ?? 0
42 for i in 0..<Int(ceil(origDur / 30)) {
43 let startSec = Double(i) * 30
44 let at = CMTime(seconds: startSec, preferredTimescale: 600)
45 let dur = CMTime(seconds: min(30, origDur - startSec),
46 preferredTimescale: 600)
47 let url = chunkDir.appendingPathComponent(String(format: "c%06d.mov", i))
48 if FileManager.default.fileExists(atPath: url.path) {
49 let chunk = AVURLAsset(url: url)
50 if let v = try? await chunk.loadTracks(withMediaType: .video).first {
51 let d = (try? await chunk.load(.duration)) ?? .zero
52 try? vTrack.insertTimeRange(
53 CMTimeRange(start: .zero, duration: min(d, dur)), of: v, at: at)
54 continue
55 }
56 }
57 if let origV {
58 try? vTrack.insertTimeRange(CMTimeRange(start: at, duration: dur),
59 of: origV, at: at)
60 }
61 }
62 let item = AVPlayerItem(asset: comp)
63 item.preferredForwardBufferDuration = 1
64 player.replaceCurrentItem(with: item)
65 while item.status != .readyToPlay {
66 try? await Task.sleep(nanoseconds: 100_000_000)
67 }
68 let t0 = CACurrentMediaTime()
69 nonisolated(unsafe) let layerRef = layer // diagnostic: read on main at completion
70 player.seek(to: CMTime(seconds: t, preferredTimescale: 60000),
71 toleranceBefore: .zero, toleranceAfter: .zero) { _ in
72 NSLog("[layertest] %@ seek landed in %.1fs ready=%d", key,
73 CACurrentMediaTime() - t0, layerRef.isReadyForDisplay ? 1 : 0)
74 }
75 }
76 }
77}
sequencer/Sources/Sequencer/Log.swift created+44
......@@ -0,0 +1,44 @@
1import Foundation
2
3/// Diagnostics that must survive without a console attached. NSLog only
4/// reaches the unified log (invisible for `open`-launched apps unless you go
5/// digging in Console.app), so the lines worth keeping — every [miss] spinner
6/// event, rescue/build failures, admission blocks — are ALSO appended to
7/// `~/Library/Logs/Sequencer.log`. Plain text, timestamped, rotated at 5 MB
8/// (one `.old` generation kept).
9enum SeqLog {
10 private static let queue = DispatchQueue(label: "sequencer.log", qos: .utility)
11 private static let url: URL = {
12 let dir = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0]
13 .appendingPathComponent("Logs", isDirectory: true)
14 try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
15 return dir.appendingPathComponent("Sequencer.log")
16 }()
17 private static let stamp: DateFormatter = {
18 let f = DateFormatter()
19 f.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
20 return f
21 }()
22
23 /// Log to the unified log (NSLog) AND the plain-text file.
24 static func log(_ format: String, _ args: CVarArg...) {
25 let line = String(format: format, arguments: args)
26 NSLog("%@", line)
27 let dated = "\(stamp.string(from: Date())) \(line)\n"
28 queue.async {
29 if let size = (try? FileManager.default.attributesOfItem(atPath: url.path)[.size]
30 as? NSNumber)?.int64Value, size > 5_000_000 {
31 let old = url.deletingPathExtension().appendingPathExtension("old.log")
32 try? FileManager.default.removeItem(at: old)
33 try? FileManager.default.moveItem(at: url, to: old)
34 }
35 if let h = FileHandle(forWritingAtPath: url.path) {
36 defer { try? h.close() }
37 _ = try? h.seekToEnd()
38 try? h.write(contentsOf: Data(dated.utf8))
39 } else {
40 try? Data(dated.utf8).write(to: url)
41 }
42 }
43 }
44}
sequencer/Sources/Sequencer/MediaPipeline.swift+350-55
......@@ -17,8 +17,8 @@ final class MediaPipeline {
1717 static let shared = MediaPipeline()
1818
1919 let cacheRoot: URL
20 /// LRU cap in bytes (default 50 GB). Override with `defaults write
21 /// com.sequencer maxCacheGB -int 100`.
20 /// LRU cap in bytes (default 50 GB). Override in Settings, or `defaults
21 /// write net.paperclover.Sequencer maxCacheGB -int 100`.
2222 var maxCacheBytes: Int64 {
2323 let gb = UserDefaults.standard.integer(forKey: "maxCacheGB")
2424 return Int64(gb > 0 ? gb : 50) * 1_000_000_000
......@@ -28,7 +28,7 @@ final class MediaPipeline {
2828 private let ffprobe: String?
2929 private let workQueue = OperationQueue()
3030 private var statuses: [UUID: MediaStatus] = [:] // main-thread only
31 private let thumbCache = NSCache<NSString, NSImage>()
31 private let thumbCache = NSCache<NSString, CGImage>()
3232 private var stripInfoCache: [String: (interval: Double, count: Int)] = [:]
3333 private var lruTouched: [String: Date] = [:]
3434
......@@ -44,6 +44,12 @@ final class MediaPipeline {
4444 ffprobe = Self.findExecutable("ffprobe")
4545 workQueue.maxConcurrentOperationCount = 2
4646 thumbCache.countLimit = 2000
47 // A previous instance killed mid-build (rebuild relaunch, force quit)
48 // leaves orphaned ffmpeg encoders holding shared VideoToolbox decode
49 // sessions — enough of them and every AVPlayer here renders black.
50 Self.reapOrphans(cacheRoot: cacheRoot)
51 // Measure the cache once at launch; chunk builds wait on `budgetReady`.
52 DispatchQueue.main.async { self.reconcileLedger() }
4753 }
4854
4955 static func findExecutable(_ name: String) -> String? {
......@@ -68,6 +74,18 @@ final class MediaPipeline {
6874 return s
6975 }
7076
77 private var offlineCache: [String: (offline: Bool, until: Date)] = [:] // main-thread only
78 /// Whether the media's ORIGINAL file is currently unreachable (moved, or on
79 /// an unmounted NAS). Cached briefly so the viewer can call it every frame
80 /// without a `stat` each time, and so it auto-recovers when the drive returns.
81 func isOffline(_ media: MediaItem) -> Bool {
82 let now = Date()
83 if let c = offlineCache[media.cacheKey], c.until > now { return c.offline }
84 let off = !FileManager.default.fileExists(atPath: media.path)
85 offlineCache[media.cacheKey] = (off, now.addingTimeInterval(2))
86 return off
87 }
88
7189 // MARK: - Cache paths
7290
7391 /// The shape `cacheKey(for:)` produces: exactly 16 lowercase hex chars. A
......@@ -236,25 +254,71 @@ final class MediaPipeline {
236254 "aformat=channel_layouts=mono,showwavespic=s=2048x200:colors=white",
237255 "-frames:v", "1", waveformURL(media.cacheKey).path,
238256 ])
257 let bytes = (try? FileManager.default.attributesOfItem(
258 atPath: waveformURL(media.cacheKey).path)[.size] as? NSNumber)?.int64Value ?? 0
239259 DispatchQueue.main.async {
240260 self.touchLRU(media.cacheKey)
241261 if res.exitCode == 0 {
242 NotificationCenter.default.post(name: .mediaStatusChanged, object: nil)
262 self.noteBytesAdded(bytes)
263 self.imgStateLock.lock()
264 self.waveformMissing.remove(media.cacheKey)
265 self.imgStateLock.unlock()
266 // `scene: true` — a waveform image changes timeline pixels
267 // (the tile cache flushes on it; plain chunk churn must not).
268 NotificationCenter.default.post(name: .mediaStatusChanged, object: nil,
269 userInfo: ["scene": true])
243270 }
244271 }
245272 }
246273
247 private let waveformCache = NSCache<NSString, NSImage>()
274 private let waveformCache = NSCache<NSString, CGImage>()
275
276 /// Decode an image file straight into the display's raster format (BGRA
277 /// premultiplied, screen colorspace). A plain NSImage/CGImageSource image
278 /// keeps the file's own format (RGB JPEG, generic colorspace), and Quartz
279 /// then converts it on EVERY draw — per thumbnail, per frame. Converting
280 /// once at load makes the timeline's image blits plain memory copies.
281 static func displayImage(contentsOf url: URL) -> CGImage? {
282 guard let src = CGImageSourceCreateWithURL(url as CFURL, nil),
283 let raw = CGImageSourceCreateImageAtIndex(
284 src, 0, [kCGImageSourceShouldCache: false] as CFDictionary)
285 else { return nil }
286 let space = NSScreen.main?.colorSpace?.cgColorSpace
287 ?? CGColorSpace(name: CGColorSpace.sRGB)!
288 guard let ctx = CGContext(
289 data: nil, width: raw.width, height: raw.height,
290 bitsPerComponent: 8, bytesPerRow: 0, space: space,
291 bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue
292 | CGBitmapInfo.byteOrder32Little.rawValue) else { return raw }
293 ctx.draw(raw, in: CGRect(x: 0, y: 0, width: raw.width, height: raw.height))
294 return ctx.makeImage() ?? raw
295 }
248296
249 func waveformImage(for media: MediaItem) -> NSImage? {
250 let key = media.cacheKey as NSString
251 if let img = waveformCache.object(forKey: key) { return img }
252 let url = waveformURL(media.cacheKey)
297 /// Same negative/in-flight bookkeeping as thumbnails: a missing waveform
298 /// must not re-dispatch a load per audio clip per frame.
299 private var waveformMissing = Set<String>()
300 private var waveformLoading = Set<String>()
301
302 func waveformImage(for media: MediaItem) -> CGImage? {
303 let key = media.cacheKey
304 if let img = waveformCache.object(forKey: key as NSString) { return img }
305 imgStateLock.lock()
306 let skip = waveformMissing.contains(key) || waveformLoading.contains(key)
307 if !skip { waveformLoading.insert(key) }
308 imgStateLock.unlock()
309 guard !skip else { return nil }
310 let url = waveformURL(key)
253311 DispatchQueue.global(qos: .utility).async {
254 guard let img = NSImage(contentsOf: url) else { return }
312 let img = Self.displayImage(contentsOf: url)
255313 DispatchQueue.main.async {
256 self.waveformCache.setObject(img, forKey: key)
257 self.notifyThumbsCoalesced()
314 self.imgStateLock.lock()
315 self.waveformLoading.remove(key)
316 if img == nil { self.waveformMissing.insert(key) }
317 self.imgStateLock.unlock()
318 if let img {
319 self.waveformCache.setObject(img, forKey: key as NSString)
320 self.notifyThumbsCoalesced()
321 }
258322 }
259323 }
260324 return nil
......@@ -279,41 +343,72 @@ final class MediaPipeline {
279343 try? d.write(to: stripInfoURL(media.cacheKey))
280344 }
281345 }
346 let bytes = Self.directorySize(dir)
282347 DispatchQueue.main.async {
283348 var s = self.status(for: media)
284349 s.filmstripReady = res.exitCode == 0 && count > 0
285350 self.statuses[media.id] = s
286351 self.touchLRU(media.cacheKey)
287 NotificationCenter.default.post(name: .mediaStatusChanged, object: nil)
352 self.noteBytesAdded(bytes)
353 self.imgStateLock.lock()
354 self.thumbMissing.removeAll() // a fresh strip supersedes misses
355 self.imgStateLock.unlock()
356 NotificationCenter.default.post(name: .mediaStatusChanged, object: nil,
357 userInfo: ["scene": true])
288358 }
289359 }
290360
291361 // MARK: - Filmstrip access
292362
293363 func filmstripInfo(_ key: String) -> (interval: Double, count: Int)? {
294 if let c = stripInfoCache[key] { return c }
364 imgStateLock.lock()
365 let cached = stripInfoCache[key]
366 imgStateLock.unlock()
367 if let cached { return cached }
295368 guard let data = try? Data(contentsOf: stripInfoURL(key)),
296369 let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
297370 let interval = json["interval"] as? Double,
298371 let count = json["count"] as? Int else { return nil }
372 imgStateLock.lock()
299373 stripInfoCache[key] = (interval, count)
374 imgStateLock.unlock()
300375 return (interval, count)
301376 }
302377
303378 /// Cached thumbnail nearest to `seconds`; loads async and posts a single
304379 /// coalesced .mediaStatusChanged when thumbs land (a post per thumb
305380 /// cascades into an app-wide refresh storm while a filmstrip streams in).
306 func filmstripImage(for media: MediaItem, at seconds: Double) -> NSImage? {
381 /// Thumbs that failed to load (file absent/evicted) or are mid-load.
382 /// Without these a dense timeline re-dispatches a load per missing thumb
383 /// per FRAME — hundreds of no-op queue hops every draw. Misses are
384 /// forgotten whenever fresh thumbs land (strips may have regenerated).
385 /// Lock-guarded: the timeline rasterizes lanes on parallel threads.
386 private var thumbMissing = Set<String>()
387 private var thumbLoading = Set<String>()
388 private let imgStateLock = NSLock()
389
390 func filmstripImage(for media: MediaItem, at seconds: Double) -> CGImage? {
307391 guard let info = filmstripInfo(media.cacheKey) else { return nil }
308392 let index = min(info.count, max(1, Int(seconds / info.interval) + 1))
309 let cacheId = "\(media.cacheKey)/\(index)" as NSString
310 if let img = thumbCache.object(forKey: cacheId) { return img }
393 let cacheId = "\(media.cacheKey)/\(index)"
394 if let img = thumbCache.object(forKey: cacheId as NSString) { return img }
395 imgStateLock.lock()
396 let skip = thumbMissing.contains(cacheId) || thumbLoading.contains(cacheId)
397 if !skip { thumbLoading.insert(cacheId) }
398 imgStateLock.unlock()
399 guard !skip else { return nil }
311400 let url = stripDir(media.cacheKey).appendingPathComponent(String(format: "%06d.jpg", index))
312401 DispatchQueue.global(qos: .utility).async {
313 guard let img = NSImage(contentsOf: url) else { return }
402 let img = Self.displayImage(contentsOf: url)
314403 DispatchQueue.main.async {
315 self.thumbCache.setObject(img, forKey: cacheId)
316 self.notifyThumbsCoalesced()
404 self.imgStateLock.lock()
405 self.thumbLoading.remove(cacheId)
406 if img == nil { self.thumbMissing.insert(cacheId) }
407 self.imgStateLock.unlock()
408 if let img {
409 self.thumbCache.setObject(img, forKey: cacheId as NSString)
410 self.notifyThumbsCoalesced()
411 }
317412 }
318413 }
319414 return nil
......@@ -325,7 +420,11 @@ final class MediaPipeline {
325420 thumbNotifyPending = true
326421 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
327422 self.thumbNotifyPending = false
328 NotificationCenter.default.post(name: .mediaStatusChanged, object: nil)
423 self.imgStateLock.lock()
424 self.thumbMissing.removeAll() // strips may have (re)generated
425 self.imgStateLock.unlock()
426 NotificationCenter.default.post(name: .mediaStatusChanged, object: nil,
427 userInfo: ["scene": true])
329428 }
330429 }
331430
......@@ -341,52 +440,202 @@ final class MediaPipeline {
341440
342441 private var evicting = false
343442
344 /// LRU eviction under the byte cap. Size scan and deletion run off-main
345 /// (the cache walk is I/O); never touches the current project's media.
346 func evictIfNeeded() {
347 guard !evicting else { return }
443 // MARK: - Cache budget ledger
444
445 /// Approximate cache size in bytes, maintained incrementally (builds add,
446 /// evictions subtract) and reconciled against a real walk at launch and on
447 /// document open. With the ledger, staying under the cap is enforced BEFORE
448 /// bytes hit the disk (`tryReserve`), and the per-build full-cache walk the
449 /// old eviction did is gone. Main-thread only.
450 private(set) var ledgerBytes: Int64 = 0
451 /// Bytes promised to in-flight chunk builds; released on completion.
452 private var reservedBytes: Int64 = 0
453 /// False until the first reconcile walk finishes. Admission stays closed
454 /// while false, so an unmeasured cache can never be built past the cap.
455 private(set) var budgetReady = false
456 private var reconciling = false
457
458 /// When eviction runs at all it frees down to here (not just under the
459 /// cap), so it works in batches instead of one chunk per build.
460 var lowWatermarkBytes: Int64 { Int64(Double(maxCacheBytes) * 0.92) }
461
462 /// Re-measure the cache and set the ledger to ground truth. Called at
463 /// launch, on document open, and when the cap changes; incremental updates
464 /// keep it honest in between.
465 /// Anything on disk with an mtime before this was left by a previous
466 /// session — the test for purging session-transient files (rescue slices,
467 /// crashed builds' .partial temps) without touching live ones.
468 private static let processStart = Date()
469
470 func reconcileLedger() {
471 guard !reconciling else { return }
472 reconciling = true
473 let root = cacheRoot
474 DispatchQueue.global(qos: .utility).async {
475 Self.purgeSessionTransients(root: root)
476 let total = Self.directorySize(root)
477 DispatchQueue.main.async {
478 NSLog("[cache] reconcile: %.2f GB on disk (cap %.0f GB)",
479 Double(total) / 1e9, Double(self.maxCacheBytes) / 1e9)
480 self.ledgerBytes = total
481 self.budgetReady = true
482 self.reconciling = false
483 if self.ledgerBytes + self.reservedBytes > self.maxCacheBytes {
484 self.evictToFit(need: 0) { _ in }
485 }
486 for c in DocumentContext.allLive { c.chunks.budgetChanged() }
487 }
488 }
489 }
490
491 /// Reserve room for a build about to start. The reservation counts against
492 /// the ceiling alongside bytes already on disk, so two concurrent builds
493 /// can't both squeeze into the same headroom. Window builds reserve up to
494 /// the cap; background fill only up to the low watermark — the band in
495 /// between is slack for playhead work, so fill can never trigger (or
496 /// refight) an eviction. Main thread.
497 func tryReserve(bytes: Int64, upTo ceiling: Int64) -> Bool {
498 guard budgetReady, ledgerBytes + reservedBytes + bytes <= ceiling else { return false }
499 reservedBytes += bytes
500 return true
501 }
502
503 /// A reserved build finished: release its reservation and record what
504 /// actually landed on disk (new file minus any replaced one; 0 on failure).
505 func commitBuild(reserved: Int64, delta: Int64) {
506 reservedBytes = max(0, reservedBytes - reserved)
507 ledgerBytes = max(0, ledgerBytes + delta)
508 }
509
510 /// Bytes written outside the reservation flow (filmstrips, waveforms —
511 /// small, but the ledger should still see them between reconciles).
512 func noteBytesAdded(_ bytes: Int64) { ledgerBytes += bytes }
513
514 /// Cheap budget check for the old trigger sites (build completions, doc
515 /// open, manual). `reconcile: true` re-walks the disk first — use it when
516 /// ground truth matters (doc open, cap change, menu action).
517 func evictIfNeeded(reconcile: Bool = false) {
518 if reconcile || !budgetReady { reconcileLedger(); return }
519 if ledgerBytes + reservedBytes > maxCacheBytes {
520 evictToFit(need: 0) { _ in }
521 }
522 }
523
524 /// Free cache space so `need` more bytes fit under `ceiling` (the cap for
525 /// playhead work, the low watermark for fill), evicting down to the low
526 /// watermark once it runs at all. Coldness order: whole cache dirs of
527 /// projects no window has open (LRU) first, then — when `openDocChunks` —
528 /// individual cold proxy chunks of open documents, never touching any
529 /// document's working set (see ChunkManager.evictionCandidates).
530 /// `completion(true)` on main once the space exists.
531 func evictToFit(need: Int64, openDocChunks: Bool = true,
532 upTo ceiling: Int64? = nil,
533 completion: @escaping (Bool) -> Void) {
534 let ceiling = ceiling ?? maxCacheBytes
535 guard budgetReady, !evicting else { completion(false); return }
536 let deficit = (ledgerBytes + reservedBytes + need) - lowWatermarkBytes
537 guard deficit > 0 else { completion(true); return }
348538 evicting = true
349 // Protect the media of every open document (not just the front one) so
350 // eviction can't drop cache another window is still using.
539 // Whole-dir eviction must not touch any open document's media — those
540 // dirs also hold filmstrips/waveforms other windows are showing. Their
541 // cold CHUNKS are reclaimed individually via the candidates instead.
351542 let inUse = Set(DocumentContext.allLive.flatMap { $0.store.project.media.map(\.cacheKey) })
543 var chunkCands: [ChunkManager.EvictionCandidate] = []
544 if openDocChunks {
545 for c in DocumentContext.allLive { chunkCands += c.chunks.evictionCandidates() }
546 chunkCands.sort { $0.coldness > $1.coldness }
547 }
352548 let root = cacheRoot
353 let cap = maxCacheBytes
354549 DispatchQueue.global(qos: .utility).async {
355550 let fm = FileManager.default
356 var evicted: [String] = []
357 defer {
358 DispatchQueue.main.async {
359 for k in evicted {
360 self.stripInfoCache[k] = nil
361 self.enqueued.remove(k)
362 }
363 for c in DocumentContext.allLive { c.chunks.forget(keys: evicted) }
364 self.evicting = false
551 var freed: Int64 = 0
552 var evictedDirs: [String] = []
553 var evictedChunks: [String: [Int]] = [:]
554 // 1. Closed projects' whole cache dirs, least recently used first.
555 if let keys = try? fm.contentsOfDirectory(atPath: root.path) {
556 var entries: [(key: String, lastUsed: Double)] = []
557 for key in keys where !inUse.contains(key) {
558 let dir = root.appendingPathComponent(key, isDirectory: true)
559 var isDir: ObjCBool = false
560 guard fm.fileExists(atPath: dir.path, isDirectory: &isDir), isDir.boolValue else { continue }
561 let lastUsed = Double((try? String(contentsOf: dir.appendingPathComponent("lastUsed"), encoding: .utf8)) ?? "") ?? 0
562 entries.append((key, lastUsed))
563 }
564 for e in entries.sorted(by: { $0.lastUsed < $1.lastUsed }) {
565 guard freed < deficit else { break }
566 let dir = root.appendingPathComponent(e.key, isDirectory: true)
567 let bytes = Self.directorySize(dir)
568 try? fm.removeItem(at: dir)
569 evictedDirs.append(e.key)
570 freed += bytes
365571 }
366572 }
367 guard let keys = try? fm.contentsOfDirectory(atPath: root.path) else { return }
368 var entries: [(key: String, bytes: Int64, lastUsed: Double)] = []
369 var total: Int64 = 0
370 for key in keys {
371 let dir = root.appendingPathComponent(key, isDirectory: true)
372 var isDir: ObjCBool = false
373 guard fm.fileExists(atPath: dir.path, isDirectory: &isDir), isDir.boolValue else { continue }
374 let bytes = Self.directorySize(dir)
375 let lastUsed = Double((try? String(contentsOf: dir.appendingPathComponent("lastUsed"), encoding: .utf8)) ?? "") ?? 0
376 total += bytes
377 entries.append((key, bytes, lastUsed))
573 // 2. Cold chunks of open documents, coldest (farthest from any
574 // playhead / off-timeline) first.
575 for c in chunkCands {
576 guard freed < deficit else { break }
577 let bytes = (try? fm.attributesOfItem(atPath: c.url.path)[.size] as? NSNumber)?.int64Value ?? 0
578 guard bytes > 0 else { continue }
579 try? fm.removeItem(at: c.url)
580 evictedChunks[c.key, default: []].append(c.index)
581 freed += bytes
378582 }
379 guard total > cap else { return }
380 for e in entries.sorted(by: { $0.lastUsed < $1.lastUsed }) where !inUse.contains(e.key) {
381 try? fm.removeItem(at: root.appendingPathComponent(e.key, isDirectory: true))
382 evicted.append(e.key)
383 total -= e.bytes
384 if total <= cap { break }
583 DispatchQueue.main.async {
584 self.ledgerBytes = max(0, self.ledgerBytes - freed)
585 NSLog("[cache] evict: freed %.2f GB (%d dirs, %d chunks) → ledger %.2f GB",
586 Double(freed) / 1e9, evictedDirs.count,
587 evictedChunks.values.map(\.count).reduce(0, +),
588 Double(self.ledgerBytes) / 1e9)
589 self.imgStateLock.lock()
590 for k in evictedDirs {
591 self.stripInfoCache[k] = nil
592 }
593 self.imgStateLock.unlock()
594 for k in evictedDirs {
595 self.enqueued.remove(k)
596 }
597 for c in DocumentContext.allLive {
598 c.chunks.forget(keys: evictedDirs)
599 for (key, idxs) in evictedChunks {
600 c.chunks.noteEvicted(key: key, indices: idxs)
601 }
602 }
603 self.evicting = false
604 let ok = self.ledgerBytes + self.reservedBytes + need <= ceiling
605 if !evictedDirs.isEmpty || !evictedChunks.isEmpty {
606 NotificationCenter.default.post(name: .mediaStatusChanged, object: nil)
607 // Only re-pump when something was actually freed — a no-op
608 // eviction re-pumping would loop pump → evict → pump forever.
609 for c in DocumentContext.allLive { c.chunks.budgetChanged() }
610 }
611 completion(ok)
385612 }
386613 }
387614 }
388615
389 private static func directorySize(_ url: URL) -> Int64 {
616 /// Delete leftovers no session references anymore: rescue-slice files
617 /// (`rNNNNNN.mov` — their state is memory-only, so a relaunch can't know
618 /// which part of the chunk they cover) and `.partial.mov` temps from
619 /// builds a crash interrupted. Only files from BEFORE this process
620 /// started — a slice the current session just built stays.
621 private static func purgeSessionTransients(root: URL) {
622 let fm = FileManager.default
623 guard let en = fm.enumerator(at: root,
624 includingPropertiesForKeys: [.contentModificationDateKey])
625 else { return }
626 for case let f as URL in en {
627 let name = f.lastPathComponent
628 let isRescue = name.hasPrefix("r") && name.hasSuffix(".mov") && name.count == 11
629 && f.deletingLastPathComponent().lastPathComponent == "chunks"
630 let isTemp = name.hasSuffix(".partial.mov")
631 guard isRescue || isTemp else { continue }
632 let m = (try? f.resourceValues(forKeys: [.contentModificationDateKey])
633 .contentModificationDate) ?? .distantPast
634 if m < processStart { try? fm.removeItem(at: f) }
635 }
636 }
637
638 static func directorySize(_ url: URL) -> Int64 {
390639 var total: Int64 = 0
391640 if let en = FileManager.default.enumerator(at: url, includingPropertiesForKeys: [.fileSizeKey]) {
392641 for case let f as URL in en {
......@@ -400,6 +649,46 @@ final class MediaPipeline {
400649
401650 struct RunResult { var exitCode: Int32; var stdout: String }
402651
652 /// Every child process currently running, so app teardown can take them
653 /// down too. A killed Sequencer otherwise leaves its in-flight ffmpeg
654 /// encodes running as orphans — and each one holds VideoToolbox decode
655 /// sessions from a SHARED machine-wide pool. Enough accumulated orphans
656 /// (a few rebuild-relaunch cycles' worth) and every AVPlayer in the next
657 /// app instance silently renders BLACK: items park, seeks land,
658 /// isReadyForDisplay says true, no error anywhere. Diagnosed 2026-07-11
659 /// after the viewer went black with all state reporting healthy.
660 private static var liveChildren: [Int32: Process] = [:]
661 private static let childLock = NSLock()
662
663 /// Terminate every live child (normal quit AND SIGTERM — run.sh pkills
664 /// the app on every rebuild, which is exactly how orphans were minted).
665 static func terminateChildren() {
666 childLock.lock()
667 let children = Array(liveChildren.values)
668 childLock.unlock()
669 for p in children where p.isRunning { p.terminate() }
670 }
671
672 /// Kill orphaned ffmpeg processes from a PREVIOUS Sequencer instance
673 /// (crash, force-quit, kill -9 — anything terminateChildren couldn't
674 /// catch). Identified by their command line referencing our cache root,
675 /// so nothing else on the machine can match. Runs once at launch.
676 static func reapOrphans(cacheRoot: URL) {
677 let marker = cacheRoot.path
678 DispatchQueue.global(qos: .utility).async {
679 let r = run("/usr/bin/pgrep", ["-fl", "ffmpeg"])
680 var killed = 0
681 for line in r.stdout.split(separator: "\n") where line.contains(marker) {
682 guard let pid = Int32(line.prefix(while: \.isNumber)) else { continue }
683 kill(pid, SIGKILL)
684 killed += 1
685 }
686 if killed > 0 {
687 SeqLog.log("[cache] reaped %d orphaned ffmpeg encoder(s) from a previous instance", killed)
688 }
689 }
690 }
691
403692 @discardableResult
404693 static func run(_ path: String, _ args: [String],
405694 duration: Double? = nil,
......@@ -426,7 +715,13 @@ final class MediaPipeline {
426715 }
427716 do {
428717 try p.run()
718 childLock.lock()
719 liveChildren[p.processIdentifier] = p
720 childLock.unlock()
429721 p.waitUntilExit()
722 childLock.lock()
723 liveChildren.removeValue(forKey: p.processIdentifier)
724 childLock.unlock()
430725 } catch {
431726 return RunResult(exitCode: -1, stdout: "")
432727 }
sequencer/Sources/Sequencer/Model.swift+36-9
......@@ -142,16 +142,22 @@ struct ViewState: Codable, Equatable {
142142 var laneScale: Double = 1
143143 var snapping = true
144144 var showFilmstrips = true
145 var subtitles = true // clover-transcript captions overlay
145146 var previewsOnLeft = false
146147 var priorityPane: TrackRef? = nil
147148 var fusionHidden = false
148149 var fusionFocus = false
150 /// Where the playhead was parked at save. Reopening resumes here, which
151 /// also tells the proxy cache which neighbourhood to keep warm/protected
152 /// for this project while its window is in the background.
153 var playhead: Double = 0
149154
150155 init() {}
151156
152157 enum CodingKeys: String, CodingKey {
153158 case hiddenTracks, focusedTracks, trackHeights, laneScale, snapping,
154 showFilmstrips, previewsOnLeft, priorityPane, fusionHidden, fusionFocus
159 showFilmstrips, subtitles, previewsOnLeft, priorityPane,
160 fusionHidden, fusionFocus, playhead
155161 }
156162 init(from decoder: Decoder) throws {
157163 let c = try decoder.container(keyedBy: CodingKeys.self)
......@@ -161,10 +167,12 @@ struct ViewState: Codable, Equatable {
161167 laneScale = try c.decodeIfPresent(Double.self, forKey: .laneScale) ?? 1
162168 snapping = try c.decodeIfPresent(Bool.self, forKey: .snapping) ?? true
163169 showFilmstrips = try c.decodeIfPresent(Bool.self, forKey: .showFilmstrips) ?? true
170 subtitles = try c.decodeIfPresent(Bool.self, forKey: .subtitles) ?? true
164171 previewsOnLeft = try c.decodeIfPresent(Bool.self, forKey: .previewsOnLeft) ?? false
165172 priorityPane = try c.decodeIfPresent(TrackRef.self, forKey: .priorityPane)
166173 fusionHidden = try c.decodeIfPresent(Bool.self, forKey: .fusionHidden) ?? false
167174 fusionFocus = try c.decodeIfPresent(Bool.self, forKey: .fusionFocus) ?? false
175 playhead = try c.decodeIfPresent(Double.self, forKey: .playhead) ?? 0
168176 }
169177}
170178
......@@ -341,8 +349,10 @@ struct MediaItem: Codable, Equatable, Identifiable {
341349 var isAudio: Bool = false // audio-only file (no video stream)
342350 var cacheKey: String = ""
343351
344 var url: URL { URL(fileURLWithPath: path) }
345 var displayName: String { url.lastPathComponent }
352 // isDirectory:false skips URL's hidden stat() — path lives on the NAS,
353 // and per-draw stats of a cold network volume freeze the whole UI.
354 var url: URL { URL(fileURLWithPath: path, isDirectory: false) }
355 var displayName: String { (path as NSString).lastPathComponent }
346356
347357 init(path: String) { self.path = path }
348358
......@@ -418,6 +428,14 @@ struct Clip: Codable, Equatable, Identifiable {
418428 /// Source time for a timeline moment inside the clip.
419429 func sourceTime(at t: Double) -> Double { srcIn + (t - start) * speed }
420430
431 /// Inverse of `sourceTime(at:)`: the timeline moment inside the clip that
432 /// shows source second `s`. Clamped to the clip's own span so it can't jump
433 /// the playhead onto a neighbour.
434 func timelineTime(forSource s: Double) -> Double {
435 let t = speed != 0 ? start + (s - srcIn) / speed : start
436 return min(max(t, start), end)
437 }
438
421439 init(mediaId: UUID?, track: TrackRef, start: Double, srcIn: Double,
422440 duration: Double, kind: ClipKind = .video, linkId: UUID? = nil,
423441 board: Board? = nil) {
......@@ -572,14 +590,23 @@ extension ProjectModel {
572590 by: \.track)
573591 for (tref, arr) in grouped {
574592 if let ref, tref != ref { continue }
575 let sorted = arr.sorted { ($0.start, $0.id.uuidString) < ($1.start, $1.id.uuidString) }
593 // Tiebreak on the id only when starts collide — building a
594 // uuidString per comparison made sorting a dense lane allocate
595 // thousands of strings.
596 let sorted = arr.sorted {
597 $0.start != $1.start ? $0.start < $1.start
598 : $0.id.uuidString < $1.id.uuidString
599 }
576600 for i in 0..<sorted.count {
601 let a = sorted[i]
577602 for j in (i + 1)..<sorted.count {
578 let a = sorted[i], b = sorted[j]
579 if b.start < a.end - 1e-9 {
580 out.append(ClipOverlap(a: a, b: b, track: tref,
581 start: b.start, end: min(a.end, b.end)))
582 }
603 let b = sorted[j]
604 // Sorted by start: once b starts at/after a's end, every
605 // later clip does too. Without this break the scan is
606 // O(n²) per lane — and it runs on every gesture update.
607 if b.start >= a.end - 1e-9 { break }
608 out.append(ClipOverlap(a: a, b: b, track: tref,
609 start: b.start, end: min(a.end, b.end)))
583610 }
584611 }
585612 }
sequencer/Sources/Sequencer/PerfTest.swift+78-15
......@@ -44,7 +44,6 @@ func runPerfTest(path: String) {
4444 // runloop until the loads land, so we then measure real thumbnail blits.
4545 func warmThumbnails() {
4646 ctx.session.showFilmstrips = true
47 timeline.testSetScrolling(false)
4847 NSGraphicsContext.saveGraphicsState()
4948 NSGraphicsContext.current = gctx
5049 for _ in 0..<5 {
......@@ -57,20 +56,34 @@ func runPerfTest(path: String) {
5756 NSGraphicsContext.restoreGraphicsState()
5857 }
5958
60 // Pan across the first half of the timeline over `frames` steps, timing each
61 // direct draw. Returns mean ms/frame.
62 func measure(scrolling: Bool, frames: Int = 120) -> Double {
63 timeline.testSetScrolling(scrolling)
59 // Pan the timeline over `frames` steps, timing each direct draw. Returns
60 // (median, p90) ms/frame — the median resists the I/O-contention spikes
61 // that async thumbnail loads inject into a mean.
62 //
63 // Two pan patterns: `panPxPerFrame` nil teleports across half the project
64 // (cache-hostile — approximates fresh zooms and scrubber jumps); a value
65 // pans that many px per frame like a real scroll gesture.
66 func measure(frames: Int = 120, panPxPerFrame: Double? = nil)
67 -> (median: Double, p90: Double) {
6468 NSGraphicsContext.saveGraphicsState()
6569 NSGraphicsContext.current = gctx
6670 defer { NSGraphicsContext.restoreGraphicsState() }
6771 for _ in 0..<3 { timeline.testRedraw() } // warm up
68 let t0 = Date()
72 var times: [Double] = []
73 times.reserveCapacity(frames)
6974 for i in 0..<frames {
70 timeline.testSetOrigin(panSpan * Double(i) / Double(frames))
75 if let px = panPxPerFrame {
76 timeline.testSetOrigin(panSpan * 0.25
77 + Double(i) * px / timeline.testPxPerSecond)
78 } else {
79 timeline.testSetOrigin(panSpan * Double(i) / Double(frames))
80 }
81 let t0 = DispatchTime.now().uptimeNanoseconds
7182 timeline.testRedraw()
83 times.append(Double(DispatchTime.now().uptimeNanoseconds - t0) / 1e6)
7284 }
73 return Date().timeIntervalSince(t0) / Double(frames) * 1000
85 times.sort()
86 return (times[frames / 2], times[Int(Double(frames) * 0.9)])
7487 }
7588
7689 // pxPerSecond values: fit-all (everything on screen at once) up through
......@@ -84,7 +97,7 @@ func runPerfTest(path: String) {
8497 ("empty", 4000),
8598 ]
8699 ctx.session.showFilmstrips = true
87 print("mode zoom px/s visible ms/frame (fps) thumbs/frame")
100 print("mode zoom px/s visible med ms p90 ms (fps) thumbs/frame")
88101 for (name, pps) in zooms {
89102 timeline.testSetPxPerSecond(pps)
90103 timeline.testSetOrigin(0)
......@@ -93,17 +106,67 @@ func runPerfTest(path: String) {
93106 // Count thumbnails actually blitted over one profiled pan pass.
94107 DrawProf.on = true; DrawProf.thumbHits = 0
95108 NSGraphicsContext.saveGraphicsState(); NSGraphicsContext.current = gctx
96 timeline.testSetScrolling(false)
97109 for i in 0..<60 { timeline.testSetOrigin(panSpan * Double(i) / 60); timeline.testRedraw() }
98110 NSGraphicsContext.restoreGraphicsState()
99111 let thumbsPerFrame = DrawProf.thumbHits / 60
100112 DrawProf.on = false
101 for (mode, scrolling) in [("detail ", false), ("scrolling", true)] {
102 let ms = measure(scrolling: scrolling)
103 print(String(format: "%@ %-9@ %7.2f %7d %8.2f %5.0f %6d",
104 mode, name as NSString, pps, visible, ms, 1000 / max(0.001, ms),
105 scrolling ? 0 : thumbsPerFrame))
113 // A/B inside one process: the direct (Stage-1) path vs the tile cache,
114 // measured seconds apart so thermal drift can't skew the comparison.
115 // "scroll" pans 8 px/frame like a real gesture; "jump" teleports
116 // half-project strides (cache-hostile worst case).
117 let px8 = 8.0
118 for (mode, disabled, pan) in [
119 ("direct-scroll", true, px8 as Double?),
120 ("tiled·-scroll", false, px8),
121 ("direct-jump··", true, nil),
122 ("tiled·-jump··", false, nil),
123 ] {
124 TimelineView.tilesDisabled = disabled
125 _ = measure(frames: 30, panPxPerFrame: pan) // warm/fill cache
126 let ms = measure(panPxPerFrame: pan)
127 print(String(format: "%@ %-9@ %7.2f %7d %8.2f %8.2f %5.0f %6d",
128 mode, name as NSString, pps, visible, ms.median, ms.p90,
129 1000 / max(0.001, ms.median), thumbsPerFrame))
106130 }
131 // Zoom simulation: a continuous pinch around this zoom level (±1.5×,
132 // ~1.5%/frame like a real magnify stream). Every frame changes
133 // pxPerSecond, so the tile cache can never hit — this is the pure
134 // cold-render rate that 60 Hz zooming needs.
135 TimelineView.tilesDisabled = false
136 do {
137 let anchor = panSpan * 0.25
138 NSGraphicsContext.saveGraphicsState()
139 NSGraphicsContext.current = gctx
140 for _ in 0..<3 { timeline.testRedraw() }
141 var times: [Double] = []
142 DrawProf.on = true; DrawProf.reset()
143 for i in 0..<120 {
144 let phase = Double(i) / 120 * 4 * .pi
145 let f = exp(sin(phase) * 0.4) // pps swings ×0.67…×1.5
146 timeline.testSetPxPerSecond(pps * f)
147 timeline.testSetOrigin(anchor)
148 let t0 = DispatchTime.now().uptimeNanoseconds
149 timeline.testRedraw()
150 times.append(Double(DispatchTime.now().uptimeNanoseconds - t0) / 1e6)
151 }
152 DrawProf.on = false
153 NSGraphicsContext.restoreGraphicsState()
154 times.sort()
155 print(String(format: "zoom·-pinch·· %-9@ %7.2f %7d %8.2f %8.2f %5.0f",
156 name as NSString, pps, visible, times[60], times[108],
157 1000 / max(0.001, times[60])))
158 DrawProf.report(frames: 120) // per-phase cost of a cold zoom frame
159 timeline.testSetPxPerSecond(pps)
160 }
161 // Per-section breakdown of a tiled pan pass at this zoom.
162 DrawProf.on = true; DrawProf.reset()
163 DrawProf.tileRenders = 0; DrawProf.tileBlits = 0
164 _ = measure(frames: 60)
165 DrawProf.on = false
166 DrawProf.report(frames: 60)
167 print(String(format: " tiles: %.1f blits, %.2f renders /frame",
168 Double(DrawProf.tileBlits) / 60, Double(DrawProf.tileRenders) / 60))
169 TimelineView.tilesDisabled = false
107170 }
108171 exit(0)
109172}
sequencer/Sources/Sequencer/PlaybackController.swift+81-10
......@@ -51,6 +51,15 @@ final class PlaybackController {
5151 /// 60 Hz timer keeps running for the app's lifetime.
5252 deinit { timer?.invalidate() }
5353
54 /// Called from `DocumentContext.shutdown()` on document close: kill the timer
55 /// now (while `ctx` is still alive) so no `tick()` fires against a context
56 /// that's mid-teardown.
57 func stop() {
58 rate = 0
59 timer?.invalidate()
60 timer = nil
61 }
62
5463 private func tick() {
5564 // While paused nothing moves; edits and proxy completions push their
5665 // own syncs, so idle costs nothing.
......@@ -81,6 +90,7 @@ final class PlaybackController {
8190 let now = playhead
8291 let wasStopped = rate == 0
8392 rate = newRate
93 HangMonitor.playbackRate = newRate
8494 if newRate == 0 {
8595 pausedPlayhead = now
8696 playbackStartPlayhead = nil
......@@ -313,12 +323,16 @@ final class TrackPlayer {
313323 // PAUSED: a swap is a pure visual flash with no playback
314324 // benefit, so swap only to REVEAL a frame we otherwise
315325 // can't show at all — an unplayable original (DNx) whose
316 // covering chunk has just landed. Never swap to merely
317 // sharpen a still, and never trade a playable original
318 // (crisper than any proxy for a still) down to a proxy.
326 // covering chunk has just landed, or a playable original
327 // that still hasn't managed to park on the frame (a slow
328 // NAS seek — the cell is showing a stand-in or spinner
329 // while the proxy could show the frame now). Never swap to
330 // merely sharpen a still that's already on screen.
319331 // Pending quality upgrades take effect on the next play/seek.
332 let cur = player.currentTime().seconds
333 let parkedOff = !cur.isFinite || abs(cur - sourceTime) > 0.5
320334 swap = sharperProxy && had == nil
321 && !ctx.chunks.originalPlayable(media: media)
335 && (!ctx.chunks.originalPlayable(media: media) || parkedOff)
322336 } else {
323337 // PLAYING: adopt the proxy the moment it covers the playhead
324338 // (a NAS original may stutter), throttled so rapid
......@@ -352,7 +366,11 @@ final class TrackPlayer {
352366 /// pass) seeks it to the live position and resumes. Without the hold it
353367 /// blips wrong content from the file's head — audibly so on the audio track.
354368 private func installItem(_ item: AVPlayerItem) {
355 item.preferredForwardBufferDuration = lenientSync ? 8 : 1
369 // Video buffer depth scales with the RAM budget (Settings → Global):
370 // the default 2 GB keeps the old 1s; a workstation-sized budget buys
371 // deeper read-ahead, which is what smooths NAS originals.
372 item.preferredForwardBufferDuration = lenientSync ? 8
373 : Double(min(8, max(1, FrameCache.ramGB / 2)))
356374 let wasPlaying = player.rate != 0
357375 player.replaceCurrentItem(with: item)
358376 if wasPlaying { player.rate = 0 }
......@@ -438,8 +456,13 @@ final class VideoTrackPlayer {
438456 var currentClipId: UUID? { front.currentClipId }
439457
440458 /// Spin up the next clip's buffer this many seconds before its cut — enough
441 /// to load + decode the first frame even off a NAS original.
442 private static let preroll = 1.5
459 /// to load + decode the first frame even off a NAS original or a stitched
460 /// composition whose seek has to open a fresh chunk file.
461 private static let preroll = 4.0
462 /// Warm exact cut frames (heads AND tails) into the RAM frame cache when
463 /// their boundary is within this many seconds of the playhead, so even a
464 /// swap that outruns the preroll has the right frame to stand in.
465 private static let warmRadius = 10.0
443466
444467 func sync(ref: TrackRef, playhead: Double, rate: Double,
445468 playheadMoved: Bool, force: Bool) {
......@@ -462,9 +485,11 @@ final class VideoTrackPlayer {
462485 }
463486 }
464487
465 // BACK: preroll the next different-media clip when its cut is imminent,
466 // parked (rate 0) and muted at its first frame. Forward playback only —
467 // a reverse/scrub cut falls back to the plain (tiny-gap) swap.
488 // BACK: preroll the boundary clip when its cut is imminent, parked
489 // (rate 0) and muted at the frame the cut lands on: forward playback
490 // prerolls the NEXT different-media clip at its first frame; reverse
491 // prerolls the PREVIOUS one at its last, so a backwards cut is just as
492 // gapless.
468493 var buffering = false
469494 if rate >= 0, let next = nextDifferentClip(on: ref, after: playhead, current: cur),
470495 next.start - playhead <= Self.preroll,
......@@ -475,10 +500,46 @@ final class VideoTrackPlayer {
475500 back.player.isMuted = true
476501 back.syncTime(expected: next.srcIn, rate: 0, force: false)
477502 buffering = true
503 } else if rate < 0, let prev = prevDifferentClip(on: ref, before: playhead, current: cur),
504 playhead - prev.end <= Self.preroll,
505 let prevMedia = project.media(prev.mediaId) {
506 let tail = max(prev.srcIn, prev.sourceTime(at: prev.end) - 0.05)
507 if back.currentClipId != prev.id {
508 back.setClip(prev, media: prevMedia, sourceTime: tail)
509 }
510 back.player.isMuted = true
511 back.syncTime(expected: tail, rate: 0, force: false)
512 buffering = true
478513 }
479514 if !buffering, back.currentClipId != nil {
480515 back.setClip(nil, media: nil) // release the idle buffer
481516 }
517
518 warmBoundaryFrames(ref: ref, playhead: playhead, project: project)
519 }
520
521 /// Warm the exact frames every nearby cut will need into the RAM frame
522 /// cache — clip heads for forward crossings, clip tails for reverse — so
523 /// the beat between "boundary crossed" and "player ready" shows the real
524 /// frame instead of a filmstrip thumb or a spinner. Self-deduping (the
525 /// cache remembers, decodes are backgrounded), so per-tick calls are cheap.
526 private func warmBoundaryFrames(ref: TrackRef, playhead: Double, project: ProjectModel) {
527 for clip in project.clips where clip.track == ref && clip.kind == .video {
528 guard abs(clip.start - playhead) <= Self.warmRadius
529 || abs(clip.end - playhead) <= Self.warmRadius,
530 let media = project.media(clip.mediaId), !media.isAudio else { continue }
531 if abs(clip.start - playhead) <= Self.warmRadius {
532 FrameCache.shared.warm(
533 mediaKey: media.cacheKey, at: clip.srcIn,
534 source: ctx.chunks.frameSource(media: media, sourceTime: clip.srcIn))
535 }
536 if abs(clip.end - playhead) <= Self.warmRadius {
537 let tail = max(clip.srcIn, clip.sourceTime(at: clip.end) - 0.05)
538 FrameCache.shared.warm(
539 mediaKey: media.cacheKey, at: tail,
540 source: ctx.chunks.frameSource(media: media, sourceTime: tail))
541 }
542 }
482543 }
483544
484545 /// The next clip on this track (in time) whose media differs from `current`
......@@ -492,6 +553,16 @@ final class VideoTrackPlayer {
492553 .min { $0.start < $1.start }
493554 }
494555
556 /// The mirror for reverse playback: the previous clip (in time) whose media
557 /// differs — the cut a rewinding playhead will cross next.
558 private func prevDifferentClip(on ref: TrackRef, before t: Double,
559 current: Clip?) -> Clip? {
560 ctx.store.project.clips
561 .filter { $0.track == ref && $0.kind == .video && $0.end < t + 1e-6
562 && $0.mediaId != current?.mediaId }
563 .max { $0.end < $1.end }
564 }
565
495566 func clear() {
496567 a.player.replaceCurrentItem(with: nil)
497568 b.player.replaceCurrentItem(with: nil)
sequencer/Sources/Sequencer/SessionState.swift+13
......@@ -12,6 +12,10 @@ final class SessionState {
1212
1313 var snapping = true { didSet { postViewOptions() } }
1414 var showFilmstrips = true { didSet { postViewOptions() } }
15 /// Show the clover-recording transcript as synchronized captions over the
16 /// viewer (only ever visible when a `cam.mov` with a sibling `transcript.json`
17 /// is under the playhead).
18 var subtitlesEnabled = true { didSet { postViewOptions() } }
1519 /// Vertical zoom: multiplies the base lane height for all tracks.
1620 var laneScale: CGFloat = 1 {
1721 didSet {
......@@ -24,6 +28,11 @@ final class SessionState {
2428
2529 var hiddenTracks: Set<TrackRef> = [] { didSet { postViewOptions() } }
2630 var focusedTracks: Set<TrackRef> = [] { didSet { postViewOptions() } }
31 /// The timeline span currently on screen (set by TimelineView as it
32 /// scrolls/zooms). The proxy builder treats it as a heat anchor — the
33 /// user scrubs inside what they can see. Not persisted; no notification
34 /// (read lazily by the chunk scheduler).
35 var visibleTimeRange: ClosedRange<Double>?
2736 /// The Fusion comps band gets its own hide/focus.
2837 var fusionHidden = false { didSet { postViewOptions() } }
2938 var fusionFocus = false { didSet { postViewOptions() } }
......@@ -101,10 +110,12 @@ final class SessionState {
101110 v.laneScale = Double(laneScale)
102111 v.snapping = snapping
103112 v.showFilmstrips = showFilmstrips
113 v.subtitles = subtitlesEnabled
104114 v.previewsOnLeft = previewsOnLeft
105115 v.priorityPane = priorityPane
106116 v.fusionHidden = fusionHidden
107117 v.fusionFocus = fusionFocus
118 v.playhead = ctx.playback.playhead
108119 return v
109120 }
110121
......@@ -117,10 +128,12 @@ final class SessionState {
117128 laneScale = CGFloat(v.laneScale)
118129 snapping = v.snapping
119130 showFilmstrips = v.showFilmstrips
131 subtitlesEnabled = v.subtitles
120132 previewsOnLeft = v.previewsOnLeft
121133 priorityPane = v.priorityPane
122134 fusionHidden = v.fusionHidden
123135 fusionFocus = v.fusionFocus
136 if v.playhead > 0 { ctx.playback.seek(to: v.playhead) }
124137 }
125138
126139 private func postViewOptions() {
sequencer/Sources/Sequencer/Store.swift+85-14
......@@ -1,4 +1,4 @@
1import Foundation
1import AppKit
22
33extension Notification.Name {
44 static let projectChanged = Notification.Name("projectChanged")
......@@ -36,8 +36,18 @@ final class Store {
3636 var playhead: Double
3737 }
3838
39 private var undoStack: [Snapshot] = []
40 private var redoStack: [Snapshot] = []
39 /// One step on the undo timeline. Model edits and storyboard DRAWING edits
40 /// share this single stack so ⌘Z / ⌘⇧Z step through them in the exact order
41 /// they happened — regardless of which window is focused — and drawing is
42 /// redoable like everything else. (Raster pixels live in `BoardStore`, out of
43 /// the value-type model; the entry just carries the image to restore.)
44 private enum UndoEntry {
45 case model(Snapshot)
46 case raster(boardId: UUID, image: NSImage?) // nil image = board was blank
47 }
48
49 private var undoStack: [UndoEntry] = []
50 private var redoStack: [UndoEntry] = []
4151 private var gestureBase: Snapshot?
4252
4353 var canUndo: Bool { !undoStack.isEmpty || gestureBase != nil }
......@@ -70,6 +80,7 @@ final class Store {
7080 project = copy
7181 pruneSelection()
7282 changed()
83 noteEditLocus(before: before.model, after: copy)
7384 }
7485
7586 /// Continuous-gesture mutations (drags): one undo entry for the whole
......@@ -97,24 +108,58 @@ final class Store {
97108 /// base snapshot and would discard the gesture's changes.
98109 func endGesture(finalize: ((inout ProjectModel) -> Void)? = nil) {
99110 guard let base = gestureBase else { return }
111 // Did the drag move anything on screen (mid-gesture posts drew it)?
112 let diverged = project != base.model
100113 var copy = project
101114 finalize?(&copy)
102115 copy.normalizeStoryboards()
103116 // A drag that vacated a bottom lane leaves it as an ephemeral drop
104117 // target — collapse it (interior lanes and the clip's own lane stay).
105118 copy.pruneTrailingEmptyTracks()
106 if copy != project {
107 project = copy
108 post(.projectChanged)
109 }
119 project = copy
110120 gestureBase = nil
121 // Post `.projectChanged` exactly once. A net change goes through
122 // `changed()` (undo + dirty + the post). A net-ZERO drag that still moved
123 // things mid-gesture (dragged out and back, ephemeral lane pruned) has no
124 // undo entry, but the view is drawing the mid-gesture state — refresh it.
125 // A no-op gesture (a click that never dragged) posts nothing.
111126 if project != base.model {
112127 pushUndo(base)
128 noteEditLocus(before: base.model, after: project)
113129 pruneSelection()
114130 changed()
131 } else if diverged {
132 post(.projectChanged)
115133 }
116134 }
117135
136 /// Report where an edit landed on the timeline — the proxy builder keeps
137 /// chunks around recent edit sites warm (built early, evicted late), since
138 /// editors scrub and re-play around where they're cutting.
139 private func noteEditLocus(before: ProjectModel, after: ProjectModel) {
140 var old: [UUID: Clip] = [:]
141 for c in before.clips { old[c.id] = c }
142 var times: [Double] = []
143 func add(_ t: Double) {
144 guard times.count < 4,
145 !times.contains(where: { abs($0 - t) < 30 }) else { return }
146 times.append(t)
147 }
148 for c in after.clips where c.kind == .video && old[c.id] != c {
149 if times.count >= 4 { break }
150 guard let o = old[c.id] else { add(c.start); continue } // new clip
151 // Warm the EDGE that moved: a right-trim on a long clip should
152 // anchor at its out point, not the (possibly minutes-away) head.
153 let durChanged = abs(o.duration - c.duration) > 1e-9
154 if durChanged, abs(o.end - c.end) > 1e-9 { add(c.end) } // tail edit
155 if abs(o.srcIn - c.srcIn) > 1e-9 || abs(o.start - c.start) > 1e-9
156 || !durChanged {
157 add(c.start) // head edit, slip, move, or non-geometry change
158 }
159 }
160 ctx.chunks.noteEdits(times: times)
161 }
162
118163 /// Live, non-undoable edit for a floating control that has no discrete
119164 /// start/end (the colour picker). Unlike a gesture it holds no open state,
120165 /// so timeline edits mid-preview can't trip the gesture precondition.
......@@ -143,19 +188,45 @@ final class Store {
143188
144189 func undo() {
145190 if gestureBase != nil { cancelGesture(); return }
146 guard let prev = undoStack.popLast() else { return }
147 redoStack.append(snapshot())
148 restore(prev)
191 guard let entry = undoStack.popLast() else { return }
192 switch entry {
193 case .model(let snap):
194 redoStack.append(.model(snapshot()))
195 restore(snap)
196 case .raster(let boardId, let image):
197 // Swap: what's on screen now becomes the redo target; restore the
198 // stored (pre-edit) drawing.
199 redoStack.append(.raster(boardId: boardId, image: ctx.boards.rasterSnapshot(boardId)))
200 ctx.boards.applyRaster(image, boardId: boardId)
201 }
149202 }
150203
151204 func redo() {
152 guard let next = redoStack.popLast() else { return }
153 undoStack.append(snapshot())
154 restore(next)
205 guard let entry = redoStack.popLast() else { return }
206 switch entry {
207 case .model(let snap):
208 undoStack.append(.model(snapshot()))
209 restore(snap)
210 case .raster(let boardId, let image):
211 undoStack.append(.raster(boardId: boardId, image: ctx.boards.rasterSnapshot(boardId)))
212 ctx.boards.applyRaster(image, boardId: boardId)
213 }
214 }
215
216 /// Register a storyboard drawing edit on the shared undo timeline — called by
217 /// `BoardStore.endStroke` after it commits a stroke. `before` is the drawing
218 /// as it stood BEFORE the stroke, so undo restores it.
219 func recordRasterEdit(boardId: UUID, before: NSImage?) {
220 undoStack.append(.raster(boardId: boardId, image: before))
221 if undoStack.count > 500 { undoStack.removeFirst() }
222 redoStack.removeAll()
223 // The stroke already marked the document dirty (via noteRasterChanged);
224 // this refreshes the Undo menu's enabled state.
225 post(.documentStateChanged)
155226 }
156227
157228 private func pushUndo(_ snapshot: Snapshot) {
158 undoStack.append(snapshot)
229 undoStack.append(.model(snapshot))
159230 if undoStack.count > 500 { undoStack.removeFirst() }
160231 redoStack.removeAll()
161232 }
sequencer/Sources/Sequencer/Storyboard.swift+18-30
......@@ -134,9 +134,9 @@ final class BoardStore {
134134 // Board coordinates everywhere: top-left origin, y down. The engine owns
135135 // the y-flip into image space so callers never think about it.
136136 private var workingRasters: [UUID: NSImage] = [:]
137 private var strokeUndo: [UUID: [NSImage?]] = [:]
138 /// Boards in the order strokes were committed (global ⌘Z routing).
139 private(set) var strokeHistory: [UUID] = []
137 /// Pre-stroke drawing captured at `beginStroke`, used by `endStroke` to push
138 /// one undo entry onto the shared `Store` timeline.
139 private var pendingStrokeBefore: [UUID: NSImage?] = [:]
140140
141141 /// Raster to DISPLAY: the in-progress stroke image when one is active.
142142 func displayRaster(_ boardId: UUID) -> NSImage? {
......@@ -153,10 +153,9 @@ final class BoardStore {
153153 }
154154
155155 func beginStroke(board: Board) {
156 var stack = strokeUndo[board.id] ?? []
157 stack.append(rasterImage(board.id)?.copy() as? NSImage)
158 if stack.count > 24 { stack.removeFirst() }
159 strokeUndo[board.id] = stack
156 // Remember the pre-stroke drawing so the commit can register one undo
157 // step on the shared timeline.
158 pendingStrokeBefore[board.id] = rasterImage(board.id)?.copy() as? NSImage
160159 workingRasters[board.id] = (rasterImage(board.id)?.copy() as? NSImage)
161160 ?? blankRaster(size: board.size)
162161 }
......@@ -192,34 +191,23 @@ final class BoardStore {
192191 func endStroke(board: Board) {
193192 guard let img = workingRasters.removeValue(forKey: board.id) else { return }
194193 saveRaster(img, boardId: board.id)
195 strokeHistory.append(board.id)
196 if strokeHistory.count > 48 { strokeHistory.removeFirst() }
197 NotificationCenter.default.post(name: .mediaStatusChanged, object: nil)
194 // Register the completed stroke on the shared undo timeline.
195 let before = pendingStrokeBefore.removeValue(forKey: board.id) ?? nil
196 ctx.store.recordRasterEdit(boardId: board.id, before: before)
197 NotificationCenter.default.post(name: .mediaStatusChanged, object: nil, userInfo: ["scene": true])
198198 }
199199
200 func canUndoStroke(_ boardId: UUID) -> Bool {
201 !(strokeUndo[boardId] ?? []).isEmpty
202 }
203 var canUndoAnyStroke: Bool {
204 strokeHistory.last.map(canUndoStroke) ?? false
200 /// A copy of the current drawing for the undo timeline (nil = blank board).
201 func rasterSnapshot(_ boardId: UUID) -> NSImage? {
202 rasterImage(boardId)?.copy() as? NSImage
205203 }
206204
207 @discardableResult
208 func undoStroke(_ boardId: UUID) -> Bool {
209 guard var stack = strokeUndo[boardId], let prev = stack.popLast() else { return false }
210 strokeUndo[boardId] = stack
205 /// Restore a drawing captured by `rasterSnapshot` (undo/redo of a stroke),
206 /// dropping any in-progress stroke and refreshing the editor + viewer.
207 func applyRaster(_ image: NSImage?, boardId: UUID) {
211208 workingRasters.removeValue(forKey: boardId)
212 saveRaster(prev, boardId: boardId)
213 if let i = strokeHistory.lastIndex(of: boardId) { strokeHistory.remove(at: i) }
214 NotificationCenter.default.post(name: .mediaStatusChanged, object: nil)
215 return true
216 }
217
218 /// Undo the most recent stroke on any board.
219 @discardableResult
220 func undoLastStroke() -> Bool {
221 guard let boardId = strokeHistory.last else { return false }
222 return undoStroke(boardId)
209 saveRaster(image, boardId: boardId)
210 NotificationCenter.default.post(name: .projectChanged, object: nil)
223211 }
224212
225213 /// Wipe the drawing layer (undoable as one stroke).
sequencer/Sources/Sequencer/StoryboardEditor.swift-18
......@@ -132,15 +132,6 @@ final class StoryboardEditor: NSObject, NSWindowDelegate {
132132 private var colorWell: NSColorWell?
133133
134134 var isKeyEditor: Bool { window != nil && NSApp.keyWindow === window }
135 var canUndoRaster: Bool { isKeyEditor && (canvas?.canUndoRaster ?? false) }
136
137 /// Raster strokes undo in their own lane while the editor is key; shape
138 /// edits ride the global Store undo like everything else.
139 func undoRasterIfKey() -> Bool {
140 guard canUndoRaster, let canvas else { return false }
141 canvas.undoRaster()
142 return true
143 }
144135
145136 /// Open on a panel belonging to `ctx`'s document. The single editor window
146137 /// re-targets to whichever document asked for it.
......@@ -381,9 +372,6 @@ final class BoardCanvas: NSView {
381372 // Raster stroke state (pixels live in BoardStore's shared stroke engine)
382373 private var strokeActive = false
383374 private var lastStrokePoint: CGPoint?
384 var canUndoRaster: Bool {
385 board.map { boards.canUndoStroke($0.id) } ?? false
386 }
387375
388376 // Shape gesture state
389377 private enum ShapeDrag { case none, create, move, resize }
......@@ -596,12 +584,6 @@ final class BoardCanvas: NSView {
596584
597585 // MARK: Raster strokes (BoardStore's engine does the pixel work)
598586
599 func undoRaster() {
600 guard let board else { return }
601 boards.undoStroke(board.id)
602 needsDisplay = true
603 }
604
605587 private func strokeSegment(from a: CGPoint, to b: CGPoint, pressure: CGFloat) {
606588 guard let board, let width = tool.strokeWidth else { return }
607589 boards.strokeSegment(
sequencer/Sources/Sequencer/Theme.swift+23
......@@ -84,4 +84,27 @@ enum Theme {
8484 static var dragHint: NSColor {
8585 pick(NSColor(calibratedWhite: 0.3, alpha: 1), NSColor(calibratedWhite: 0.55, alpha: 1))
8686 }
87
88 // Optimization strip (the thin band under the ruler). Muted so a mostly
89 // red/yellow strip informs rather than alarms.
90 /// Proxy built at the full preview-quality target.
91 static var stripFull: NSColor {
92 pick(NSColor(calibratedHue: 0.36, saturation: 0.70, brightness: 0.62, alpha: 1),
93 NSColor(calibratedHue: 0.36, saturation: 0.65, brightness: 0.60, alpha: 1))
94 }
95 /// Proxy built below target — usable now, an upgrade is still queued.
96 static var stripUsable: NSColor {
97 pick(NSColor(calibratedHue: 0.36, saturation: 0.50, brightness: 0.42, alpha: 1),
98 NSColor(calibratedHue: 0.36, saturation: 0.40, brightness: 0.72, alpha: 1))
99 }
100 /// Building right now (or a rescue slice standing in).
101 static var stripBuilding: NSColor {
102 pick(NSColor(calibratedHue: 0.13, saturation: 0.80, brightness: 0.72, alpha: 1),
103 NSColor(calibratedHue: 0.13, saturation: 0.85, brightness: 0.80, alpha: 1))
104 }
105 /// No proxy on disk (never built, evicted, or failed).
106 static var stripMissing: NSColor {
107 pick(NSColor(calibratedHue: 0.01, saturation: 0.65, brightness: 0.48, alpha: 1),
108 NSColor(calibratedHue: 0.01, saturation: 0.55, brightness: 0.75, alpha: 1))
109 }
87110}
sequencer/Sources/Sequencer/TimelineView.swift+1119-230
......@@ -7,8 +7,13 @@ enum DrawProf {
77 static var acc: [String: Double] = [:]
88 static var order: [String] = []
99 static var thumbHits = 0 // filmstrip images actually blitted (cache hits)
10 static var tileRenders = 0 // scene tiles rasterized (cache misses)
11 static var tileBlits = 0 // scene tiles blitted (cache hits + fresh)
1012 @inline(__always) static func t<T>(_ label: String, _ body: () -> T) -> T {
11 if !on { return body() }
13 // Main-thread only: lanes rasterize in parallel, and racing the
14 // accumulator dictionaries would crash. Off-main work is simply
15 // not attributed (the enclosing main-thread section still is).
16 if !on || !Thread.isMainThread { return body() }
1217 let t0 = DispatchTime.now().uptimeNanoseconds
1318 let r = body()
1419 let dt = Double(DispatchTime.now().uptimeNanoseconds - t0) / 1e6
......@@ -55,10 +60,26 @@ final class TimelineView: NSView {
5560 // New document ⇒ new model; the scene cache must rebuild even though
5661 // no `.projectChanged` fires for the context swap itself.
5762 sceneDirty = true
63 stripKey = nil
64 laneRefsCache = nil
65 flushTiles()
5866 }
5967 }
6068 private var store: Store { ctx.store }
6169 private var project: ProjectModel { ctx.store.project }
70
71 /// The model's `laneRefs` getter scans EVERY clip (`hasStoryboard`) —
72 /// O(clips). The draw path reaches it per clip drawn (`drawClip →
73 /// clipRect → laneRect`), which is quadratic on big projects: 98% CPU
74 /// redraw storms. Cache it; invalidated on `.projectChanged`/
75 /// `.viewOptionsChanged` (redraw()) and on document swap (ctx.didSet).
76 private var laneRefsCache: [TrackRef]?
77 private var laneRows: [TrackRef] {
78 if let rows = laneRefsCache { return rows }
79 let rows = project.laneRefs
80 laneRefsCache = rows
81 return rows
82 }
6283 private var playback: PlaybackController { ctx.playback }
6384 private var comps: FusionComps { ctx.comps }
6485 private var boards: BoardStore { ctx.boards }
......@@ -72,12 +93,21 @@ final class TimelineView: NSView {
7293 override init(frame: NSRect) {
7394 super.init(frame: frame)
7495 registerForDraggedTypes([.fileURL])
75 for name: Notification.Name in [.projectChanged, .selectionChanged,
76 .mediaStatusChanged, .viewOptionsChanged,
77 .compsChanged] {
96 // Only model/view-option changes invalidate the grouped-clip scene
97 // cache. Selection changes just refresh the link-mate cache, and
98 // media-status/comps changes (thumbnails landing, comps rescans) only
99 // need a repaint — none of them regroup or re-sort thousands of clips.
100 for name: Notification.Name in [.projectChanged, .viewOptionsChanged] {
78101 NotificationCenter.default.addObserver(self, selector: #selector(redraw),
79102 name: name, object: nil)
80103 }
104 NotificationCenter.default.addObserver(self, selector: #selector(selectionRedraw),
105 name: .selectionChanged, object: nil)
106 NotificationCenter.default.addObserver(self, selector: #selector(mediaRedraw(_:)),
107 name: .mediaStatusChanged, object: nil)
108 // The comps band isn't tiled — a comps rescan is repaint-only.
109 NotificationCenter.default.addObserver(self, selector: #selector(repaintOnly),
110 name: .compsChanged, object: nil)
81111 // Per-document: bound against the current (headless) ctx here, re-bound
82112 // when a real ctx is injected (see `ctx.didSet`).
83113 ctx.notify.addObserver(self, selector: #selector(playheadMoved),
......@@ -88,7 +118,37 @@ final class TimelineView: NSView {
88118
89119 required init?(coder: NSCoder) { fatalError() }
90120
91 @objc private func redraw() { sceneDirty = true; needsDisplay = true }
121 @objc private func redraw() {
122 sceneDirty = true
123 stripKey = nil
124 laneRefsCache = nil
125 flushTiles()
126 needsDisplay = true
127 }
128
129 @objc private func repaintOnly() { needsDisplay = true }
130
131 /// `.mediaStatusChanged` is posted for two very different things: scene
132 /// pixels changing (a filmstrip/waveform/board raster landed — posts carry
133 /// `scene: true`) and proxy-chunk bookkeeping (status bar / viewer badges —
134 /// no timeline pixels). Only the former invalidates the tile cache; chunk
135 /// churn during playback must not force full scene re-renders.
136 @objc private func mediaRedraw(_ note: Notification) {
137 if note.userInfo?["scene"] != nil { flushTiles() }
138 stripKey = nil // chunk states changed — recompute the strip's runs
139 needsDisplay = true
140 }
141
142 @objc private func selectionRedraw() {
143 // Link-mates of the selection outline aqua; recompute just that set —
144 // a selection click must not regroup/re-sort the whole project.
145 if !sceneDirty {
146 linkedSelectionCache = project.expandLinks(store.selection)
147 .subtracting(store.selection)
148 }
149 flushTiles() // selection tint/borders are baked into the rasters
150 needsDisplay = true
151 }
92152
93153 @objc private func playheadMoved() {
94154 // Auto-follow while playing.
......@@ -127,24 +187,31 @@ final class TimelineView: NSView {
127187 }
128188 private var frameDur: Double { 1.0 / project.fps }
129189
190 /// The optimization strip: a thin band under the ruler showing, per
191 /// timeline column, how optimized the media the visible tracks need there
192 /// is (see drawOptimizeStrip).
193 private let stripH: CGFloat = 5
130194 private var fusionBandH: CGFloat { comps.visible ? 46 : 0 }
131 private var lanesTop: CGFloat { rulerH + fusionBandH }
195 private var fusionTop: CGFloat { rulerH + stripH }
196 private var lanesTop: CGFloat { rulerH + stripH + fusionBandH }
132197
133198 private func laneHeight(_ ref: TrackRef) -> CGFloat {
134 max(24, baseLaneH * session.laneScale * (session.trackHeights[ref] ?? 1))
199 // Device-pixel quantized so lane rasters (scene tiles) blit without
200 // resampling; the ≤ half-device-pixel rounding is imperceptible.
201 quantized(max(24, baseLaneH * session.laneScale * (session.trackHeights[ref] ?? 1)))
135202 }
136 private var defaultLaneH: CGFloat { max(24, baseLaneH * session.laneScale) }
203 private var defaultLaneH: CGFloat { quantized(max(24, baseLaneH * session.laneScale)) }
137204
138205 /// Total height of all lanes (for vertical scroll clamping).
139206 private var lanesContentHeight: CGFloat {
140 project.laneRefs.reduce(laneGap) { $0 + laneHeight($1) + laneGap }
207 laneRows.reduce(laneGap) { $0 + laneHeight($1) + laneGap }
141208 }
142209 private var maxScrollY: CGFloat {
143210 max(0, lanesContentHeight - (bounds.height - lanesTop) + defaultLaneH)
144211 }
145212
146213 private func laneRect(row: Int) -> NSRect {
147 let rows = project.laneRefs
214 let rows = laneRows
148215 var y = lanesTop + laneGap - scrollY
149216 for (i, ref) in rows.enumerated() {
150217 let h = laneHeight(ref)
......@@ -161,7 +228,7 @@ final class TimelineView: NSView {
161228 private func rowAt(y: CGFloat) -> Int? {
162229 guard y > lanesTop else { return nil }
163230 var yy = lanesTop + laneGap - scrollY
164 let rows = project.laneRefs
231 let rows = laneRows
165232 for (i, ref) in rows.enumerated() {
166233 let h = laneHeight(ref)
167234 if y < yy + h + laneGap { return i }
......@@ -173,7 +240,7 @@ final class TimelineView: NSView {
173240 /// Row whose bottom edge is under the cursor (for track-height resizing).
174241 private func trackBoundaryAt(y: CGFloat) -> Int? {
175242 guard y > lanesTop else { return nil }
176 let rows = project.laneRefs
243 let rows = laneRows
177244 var yy = lanesTop + laneGap - scrollY
178245 for (i, ref) in rows.enumerated() {
179246 yy += laneHeight(ref)
......@@ -191,15 +258,17 @@ final class TimelineView: NSView {
191258
192259 /// The lane shown at a row, or nil past the last real lane (ghost rows).
193260 private func laneRef(row: Int) -> TrackRef? {
194 let rows = project.laneRefs
261 let rows = laneRows
195262 return rows.indices.contains(row) ? rows[row] : nil
196263 }
197264
198265 private func clipAt(point: NSPoint) -> (clip: Clip, row: Int)? {
199266 guard let row = rowAt(y: point.y), let ref = laneRef(row: row) else { return nil }
267 // The cached per-lane arrays are already start-sorted — no need to
268 // re-filter and re-sort the whole project per mouse event.
269 rebuildSceneIfNeeded()
200270 // Later clips draw on top, so hit-test in reverse.
201 for clip in project.clips.filter({ $0.track == ref })
202 .sorted(by: { $0.start < $1.start }).reversed() {
271 for clip in (clipsByLane[ref] ?? []).reversed() {
203272 if clipRect(clip, row: row).contains(point) { return (clip, row) }
204273 }
205274 return nil
......@@ -207,7 +276,8 @@ final class TimelineView: NSView {
207276
208277 private func overlapAt(point: NSPoint) -> ClipOverlap? {
209278 guard let row = rowAt(y: point.y), let ref = laneRef(row: row) else { return nil }
210 for o in project.overlaps(on: ref) {
279 rebuildSceneIfNeeded()
280 for o in overlapsByLane[ref] ?? [] {
211281 let lane = laneRect(row: row)
212282 let r = NSRect(x: xFor(o.start), y: lane.minY,
213283 width: max(2, xFor(o.end) - xFor(o.start)), height: lane.height)
......@@ -228,6 +298,16 @@ final class TimelineView: NSView {
228298 // large project is now a redraw of cached geometry, not a full recompute.
229299 private var clipsByLane: [TrackRef: [Clip]] = [:]
230300 private var overlapsByLane: [TrackRef: [ClipOverlap]] = [:]
301 /// Ids of clips that are part of an overlap, per lane — hoisted out of
302 /// `drawLane`, which used to rebuild this Set per lane per frame.
303 private var overlapIdsByLane: [TrackRef: Set<UUID>] = [:]
304 /// Longest clip duration per lane — bounds the binary-searched draw/culling
305 /// window (a clip can start at most this far left of the view and still be
306 /// visible).
307 private var maxClipDurByLane: [TrackRef: Double] = [:]
308 /// `project.timelineDuration` scans every clip; cached here so per-frame
309 /// chrome (scrollbars, origin clamping) doesn't rescan 4k+ clips.
310 private var cachedTimelineDuration: Double = 0
231311 private var mediaById: [UUID: MediaItem] = [:]
232312 private var sceneDirty = true
233313
......@@ -239,6 +319,43 @@ final class TimelineView: NSView {
239319 .foregroundColor: NSColor(calibratedWhite: 0.92, alpha: 1),
240320 ]
241321
322 /// Clip titles as cached CTLines. `NSString.draw` runs a full CoreText
323 /// layout per call — ~200 visible labels re-laid-out every cold frame,
324 /// even though a multicam project has a handful of distinct names. Layout
325 /// once per distinct title, then `CTLineDraw` is a glyph blit. Capped:
326 /// titles are media names + panel names, a small closed set in practice.
327 private static var titleLineCache: [String: (line: CTLine, ascent: CGFloat)] = [:]
328 private static let titleLineLock = NSLock() // lanes rasterize in parallel
329 private static func titleLine(_ s: String) -> (line: CTLine, ascent: CGFloat) {
330 titleLineLock.lock()
331 defer { titleLineLock.unlock() }
332 if let c = titleLineCache[s] { return c }
333 if titleLineCache.count > 512 { titleLineCache.removeAll(keepingCapacity: true) }
334 let line = CTLineCreateWithAttributedString(
335 NSAttributedString(string: s, attributes: titleAttrs))
336 var ascent: CGFloat = 0
337 CTLineGetTypographicBounds(line, &ascent, nil, nil)
338 let entry = (line, ascent)
339 titleLineCache[s] = entry
340 return entry
341 }
342
343 /// Draw a cached title line with its top-left at `point` in the flipped
344 /// view space — positioned to match what `NSString.draw(at:)` produced.
345 private func drawTitle(_ s: String, at point: NSPoint) {
346 guard let cg = NSGraphicsContext.current?.cgContext else { return }
347 let (line, ascent) = Self.titleLine(s)
348 cg.saveGState()
349 // Flipped context: un-flip locally for glyph drawing. The box-top to
350 // baseline offset is the line's ascent, matching NSString.draw(at:).
351 cg.translateBy(x: point.x, y: point.y + ascent)
352 cg.scaleBy(x: 1, y: -1)
353 cg.textMatrix = .identity
354 cg.textPosition = .zero
355 CTLineDraw(line, cg)
356 cg.restoreGState()
357 }
358
242359 /// SF Symbols, tinted and baked into flat bitmaps, cached by `key`. Building
243360 /// an SF Symbol and tinting it (`tinted` does a `lockFocus` composite) *per
244361 /// draw* was the single biggest timeline draw cost: nearly every clip is
......@@ -246,9 +363,12 @@ final class TimelineView: NSView {
246363 /// the track-header buttons re-baked every frame too. Cached, drawing a badge
247364 /// is a plain blit. Main-thread only (all drawing is).
248365 private static var symbolCache: [String: NSImage] = [:]
366 private static let symbolCacheLock = NSLock() // lanes rasterize in parallel
249367 static func bakedSymbol(_ name: String, pointSize: CGFloat = 0,
250368 weight: NSFont.Weight = .regular,
251369 tint: NSColor, key: String) -> NSImage? {
370 symbolCacheLock.lock()
371 defer { symbolCacheLock.unlock() }
252372 if let img = symbolCache[key] { return img }
253373 var base = NSImage(systemSymbolName: name, accessibilityDescription: name)
254374 if pointSize > 0 {
......@@ -281,25 +401,28 @@ final class TimelineView: NSView {
281401 bakedSymbol("link", tint: .white, key: "badge.link")
282402 }
283403
284 /// True while the user is actively scrolling/zooming (cleared ~120 ms after
285 /// the last scroll event, which triggers one full-detail redraw). On its own
286 /// it changes nothing — clips draw at full detail while scrolling. It only
287 /// gates the `lightScroll` fallback: a *very dense* frame (see
288 /// `denseScrollClips`) sheds the heavy filmstrip/waveform pass mid-scroll so
289 /// a pathological timeline can't stall, restoring detail the moment it settles.
290 private var isScrolling = false
291 private var scrollSettleToken = 0
292
293 /// Mark a scroll/zoom in progress and schedule the settle redraw.
294 private func noteScrolling() {
295 isScrolling = true
296 scrollSettleToken &+= 1
297 let token = scrollSettleToken
298 DispatchQueue.main.asyncAfter(deadline: .now() + 0.12) { [weak self] in
299 guard let self, self.scrollSettleToken == token else { return }
300 self.isScrolling = false
301 self.needsDisplay = true
404 /// Index range of a lane's (start-sorted) clips that can intersect the
405 /// time window — binary-searched so a frame's work is proportional to
406 /// what's on screen, not to how far into a 10-hour project it sits. The
407 /// lower bound backs off by the lane's longest clip (any clip starting
408 /// earlier than that has necessarily ended before `left`); the upper bound
409 /// is the first start past `right`. Callers still skip the odd short clip
410 /// inside the backoff span with an `end < left` check.
411 private func visibleIndexRange(_ clips: [Clip], lane: TrackRef,
412 left: Double, right: Double) -> Range<Int> {
413 let backoff = left - (maxClipDurByLane[lane] ?? 0)
414 var lo = 0, hi = clips.count
415 while lo < hi { // first index with start >= backoff
416 let mid = (lo + hi) / 2
417 if clips[mid].start < backoff { lo = mid + 1 } else { hi = mid }
418 }
419 let first = lo
420 hi = clips.count
421 while lo < hi { // first index with start > right
422 let mid = (lo + hi) / 2
423 if clips[mid].start <= right { lo = mid + 1 } else { hi = mid }
302424 }
425 return first..<lo
303426 }
304427
305428 private func rebuildSceneIfNeeded() {
......@@ -315,6 +438,13 @@ final class TimelineView: NSView {
315438 // `overlaps()` (no arg) computes every lane in one pass; grouping by
316439 // track yields the same per-lane arrays `overlaps(on:)` returned.
317440 overlapsByLane = Dictionary(grouping: project.overlaps(), by: \.track)
441 overlapIdsByLane = overlapsByLane.mapValues {
442 Set($0.flatMap { [$0.a.id, $0.b.id] })
443 }
444 maxClipDurByLane = clipsByLane.mapValues {
445 $0.map(\.duration).max() ?? 0
446 }
447 cachedTimelineDuration = project.timelineDuration
318448 mediaById = Dictionary(project.media.map { ($0.id, $0) },
319449 uniquingKeysWith: { a, _ in a })
320450 // Per-lane colours: the track colour and its black-blended body/strip
......@@ -322,7 +452,7 @@ final class TimelineView: NSView {
322452 // alloc + two colour-space `blended()` conversions) for every clip every
323453 // frame. Compute them once per lane here.
324454 laneColorCache = [:]
325 for ref in project.laneRefs {
455 for ref in laneRows {
326456 let base = trackColor(ref)
327457 laneColorCache[ref] = LaneColors(
328458 base: base,
......@@ -357,39 +487,308 @@ final class TimelineView: NSView {
357487 let left = originSecond
358488 let right = originSecond + Double(bounds.width - headerW) / pxPerSecond
359489 var n = 0
360 for ref in project.laneRefs {
361 for c in clipsByLane[ref] ?? [] {
362 if c.start > right { break }
363 if c.end < left { continue }
490 for ref in laneRows {
491 let clips = clipsByLane[ref] ?? []
492 for i in visibleIndexRange(clips, lane: ref, left: left, right: right)
493 where clips[i].end >= left {
364494 n += 1
365495 }
366496 }
367497 return n
368498 }
369499
370 /// While actively scrolling a *very dense* frame, shed the heavy filmstrip/
371 /// waveform pass so the pan stays fluid; it returns the instant the scroll
372 /// settles. Ordinary-density views keep full detail — thumbnails, waveforms
373 /// and labels — even mid-scroll, so nothing visibly "drops out" in normal use.
374 private var lightScroll = false
375 private static let denseScrollClips = 350
500 /// Below this on-screen width a clip physically cannot show its corner
501 /// radius, title strip text, badges, or a meaningful filmstrip/waveform
502 /// column — so `drawClip` takes a flat-rect fast path that produces the
503 /// same pixels for a fraction of the cost. Detail is a function of what's
504 /// resolvable at the current zoom, never of whether the user is scrolling:
505 /// this replaces the old `lightScroll` mode, which shed thumbnails and
506 /// labels mid-pan (visibly) yet saved almost nothing — the real per-clip
507 /// cost was the path/clip-state chrome that ran for sub-pixel clips.
508 private static let lodMinWidth: CGFloat = 3
509
510 // MARK: - Scene tile cache
511 //
512 // The scene (lane clips: bodies, filmstrips, waveforms, strips, labels,
513 // badges, selection tint) is rasterized into per-lane, 512-pt-wide tiles in
514 // TIMELINE space — a tile's x axis is (t − sliceStart)·pxPerSecond, which
515 // is independent of the pan origin. Panning and the 60 Hz playhead redraw
516 // therefore cost a handful of blits plus the (cheap) chrome, instead of
517 // re-rendering thousands of clips. Anything that changes scene pixels —
518 // model edits, selection, thumbnails landing, theme, zoom, lane heights —
519 // flushes; tiles rebuild lazily, visible-first, at direct-render cost.
520 // Active edit gestures bypass tiles entirely (direct draw), so a drag never
521 // thrashes the cache mid-gesture.
522
523 private struct TileKey: Hashable {
524 let ref: TrackRef
525 let slice: Int
526 }
527 /// Everything a tile's pixels depend on besides the model/selection (those
528 /// flush via notifications). A mismatch flushes the whole cache.
529 private struct TileParams: Equatable {
530 var pps: Double
531 var scale: CGFloat
532 var light: Bool
533 var heights: [TrackRef: CGFloat]
534 var showFilmstrips: Bool
535 }
536 private var tiles: [TileKey: CGImage] = [:]
537 private var tileUse: [TileKey: Int] = [:]
538 private var tileTick = 0
539 private var tileParams: TileParams?
540 private static let tileW: CGFloat = 512
541 /// ~96 tiles ≈ a few viewports of 2× lane strips (~100 MB worst case).
542 private static let maxTiles = 96
543 /// Cap on tile rasterizations per frame. A cold viewport (fresh zoom, big
544 /// jump) draws the un-cached slices directly this frame — same pixels,
545 /// direct cost — and fills the cache over the next few frames instead of
546 /// paying ~25 bitmap allocations in one frame.
547 private static let tileRendersPerFrame = 6
548 private var tileRenderBudget = 0
549 private var tileFillPending = false
550 private var lastDrawOrigin = 0.0
551 private var lastDrawPps = 0.0
552 /// Escape hatch for A/B measurement and debugging: SEQ_NOTILES=1 forces
553 /// the direct (Stage-1) render path. Mutable so --perftest can A/B both
554 /// paths inside one process (immune to thermal drift between runs).
555 static var tilesDisabled =
556 ProcessInfo.processInfo.environment["SEQ_NOTILES"] == "1"
557
558 private func flushTiles() {
559 tiles.removeAll(keepingCapacity: true)
560 tileUse.removeAll(keepingCapacity: true)
561 }
562
563 /// Device-pixel scale of the surface actually being drawn into, captured
564 /// from the context CTM at the top of `draw`. Falling back to the window's
565 /// backing scale is only right when they agree — an offscreen 1× target
566 /// (like --perftest's bitmap) on a 2× machine would otherwise get 2× tiles
567 /// downsampled on every blit.
568 private var renderScale: CGFloat = 2
569
570 private var backingScale: CGFloat {
571 window?.backingScaleFactor ?? NSScreen.main?.backingScaleFactor ?? 2
572 }
573
574 /// Quantize a length/offset to the device-pixel grid. Tiles are blitted at
575 /// integral device pixels; quantizing the shared geometry (origin, scroll,
576 /// lane heights) keeps the tile and direct paths on the same sub-pixel
577 /// phase, so their output is pixel-identical and blits never resample.
578 private func quantized(_ v: CGFloat) -> CGFloat {
579 (v * renderScale).rounded() / renderScale
580 }
581
582 private func evictTilesIfNeeded() {
583 guard tiles.count > Self.maxTiles else { return }
584 for (key, _) in tileUse.sorted(by: { $0.value < $1.value })
585 .prefix(tiles.count - Self.maxTiles) {
586 tiles[key] = nil
587 tileUse[key] = nil
588 }
589 }
590
591 /// Blit the lane's visible tiles. Missing tiles are rendered up to the
592 /// per-frame budget; past it, their span draws directly (identical pixels)
593 /// and a follow-up display pass finishes filling the cache.
594 private func blitLaneTiles(ref: TrackRef, lane: NSRect) {
595 guard let cg = NSGraphicsContext.current?.cgContext else { return }
596 let sliceDur = Double(Self.tileW) / pxPerSecond
597 let leftSec = secondsFor(headerW)
598 let rightSec = secondsFor(bounds.width)
599 var slice = Int(floor(leftSec / sliceDur))
600 let last = Int(floor(rightSec / sliceDur))
601 while slice <= last {
602 let x = xFor(Double(slice) * sliceDur)
603 if let img = tileImage(ref: ref, lane: lane, slice: slice, sliceDur: sliceDur) {
604 // CGContext blit (NSImage.draw pays rep-matching/colorspace
605 // overhead per call). Local flip: CGImages draw bottom-up.
606 cg.saveGState()
607 cg.translateBy(x: x, y: lane.maxY)
608 cg.scaleBy(x: 1, y: -1)
609 cg.draw(img, in: CGRect(x: 0, y: 0, width: Self.tileW,
610 height: lane.height))
611 cg.restoreGState()
612 if DrawProf.on { DrawProf.tileBlits += 1 }
613 } else {
614 // Over budget this frame: draw the slice's span directly.
615 NSGraphicsContext.current?.saveGraphicsState()
616 NSRect(x: x, y: lane.minY, width: Self.tileW,
617 height: lane.height).clip()
618 drawLaneClips(ref: ref, lane: lane, cullX0: x, cullX1: x + Self.tileW)
619 NSGraphicsContext.current?.restoreGraphicsState()
620 scheduleTileFill()
621 }
622 slice += 1
623 }
624 }
625
626 /// One coalesced follow-up display pass to keep rasterizing missed tiles
627 /// after a budget-limited frame.
628 private func scheduleTileFill() {
629 guard !tileFillPending else { return }
630 tileFillPending = true
631 DispatchQueue.main.async { [weak self] in
632 guard let self else { return }
633 self.tileFillPending = false
634 self.needsDisplay = true
635 }
636 }
637
638 /// The cached tile for (lane, slice), rendering it via the SAME
639 /// `drawLaneClips` code the direct path uses — pixel-equivalent by
640 /// construction. The context is translated so the slice's start lands at
641 /// x = 0 and the lane's top at y = 0; both offsets cancel the pan origin,
642 /// which is what makes the raster reusable across scroll positions.
643 private func tileImage(ref: TrackRef, lane: NSRect, slice: Int,
644 sliceDur: Double) -> CGImage? {
645 let key = TileKey(ref: ref, slice: slice)
646 tileTick += 1
647 tileUse[key] = tileTick
648 if let img = tiles[key] { return img }
649 guard tileRenderBudget > 0 else { return nil }
650 tileRenderBudget -= 1
651
652 let scale = renderScale
653 let pxW = Int((Self.tileW * scale).rounded())
654 let pxH = Int((lane.height * scale).rounded())
655 // Native Quartz raster format (BGRA premultiplied, little-endian) in
656 // the window's own colorspace: blits are then straight memory copies.
657 // An NSBitmapImageRep here (RGBA, deviceRGB) costs a per-blit swizzle
658 // + colorspace conversion — ~1.5 ms per tile, wiping out the caching.
659 let space = window?.colorSpace?.cgColorSpace
660 ?? CGColorSpace(name: CGColorSpace.sRGB)!
661 guard pxW > 0, pxH > 0,
662 let cg = CGContext(
663 data: nil, width: pxW, height: pxH, bitsPerComponent: 8,
664 bytesPerRow: 0, space: space,
665 bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue
666 | CGBitmapInfo.byteOrder32Little.rawValue) else { return nil }
667
668 NSGraphicsContext.saveGraphicsState()
669 // Map: device pixels → points, unflipped → flipped (the view draws
670 // top-down), view space → tile space. Wrapped in a `flipped: true`
671 // NSGraphicsContext so text and images orient correctly.
672 cg.scaleBy(x: scale, y: scale)
673 cg.translateBy(x: 0, y: lane.height)
674 cg.scaleBy(x: 1, y: -1)
675 let x0 = xFor(Double(slice) * sliceDur)
676 cg.translateBy(x: -x0, y: -lane.minY)
677 NSGraphicsContext.current = NSGraphicsContext(cgContext: cg, flipped: true)
678 drawLaneClips(ref: ref, lane: lane, cullX0: x0, cullX1: x0 + Self.tileW)
679 NSGraphicsContext.restoreGraphicsState()
680
681 guard let img = cg.makeImage() else { return nil }
682 tiles[key] = img
683 evictTilesIfNeeded()
684 if DrawProf.on { DrawProf.tileRenders += 1 }
685 return img
686 }
687
688 /// Live draw-time telemetry, on when the app is launched with
689 /// SEQ_DRAWPROF=1 in the environment (run the binary directly with stderr
690 /// to a file — NSLog is invisible under `open`). Prints one line per
691 /// second: draws that second and mean/max ms per draw. Purely additive so
692 /// GUI perf work can be judged against real numbers, like --perftest.
693 private static let liveProf = ProcessInfo.processInfo.environment["SEQ_DRAWPROF"] == "1"
694 private var profWindowStart = CACurrentMediaTime()
695 private var profFrames = 0
696 private var profTotalMs = 0.0
697 private var profMaxMs = 0.0
376698
377699 override func draw(_ dirtyRect: NSRect) {
700 let profT0 = Self.liveProf ? CACurrentMediaTime() : 0
701 defer {
702 if Self.liveProf {
703 let ms = (CACurrentMediaTime() - profT0) * 1000
704 profFrames += 1
705 profTotalMs += ms
706 profMaxMs = max(profMaxMs, ms)
707 let now = CACurrentMediaTime()
708 if now - profWindowStart >= 1.0 {
709 FileHandle.standardError.write(Data(String(format:
710 "[drawprof] %d draws, avg %.2f ms, max %.2f ms\n",
711 profFrames, profTotalMs / Double(profFrames),
712 profMaxMs).utf8))
713 profWindowStart = now
714 profFrames = 0
715 profTotalMs = 0
716 profMaxMs = 0
717 }
718 }
719 }
378720 Theme.timelineBg.setFill()
379721 bounds.fill()
380722
723 // Publish the on-screen time span — a heat anchor for the proxy
724 // builder (the user scrubs inside what they can see). Cheap enough
725 // to set every frame; read lazily by the chunk scheduler.
726 let visibleSpan = Double(bounds.width - headerW) / pxPerSecond
727 ctx.session.visibleTimeRange = originSecond...(originSecond + max(1, visibleSpan))
728
381729 DrawProf.t("scene") { rebuildSceneIfNeeded() }
382 lightScroll = isScrolling && visibleClipCount() > Self.denseScrollClips
383 scrollY = min(scrollY, maxScrollY)
384 let rows = project.laneRefs
730 DrawProf.t("cull") {
731 // Match the tile raster scale to the surface being drawn into.
732 if let ctm = NSGraphicsContext.current?.cgContext
733 .userSpaceToDeviceSpaceTransform {
734 let s = abs(ctm.a)
735 if s > 0.1, s != renderScale {
736 renderScale = s
737 flushTiles()
738 }
739 }
740 scrollY = quantized(min(scrollY, maxScrollY))
741 // Snap the pan origin to the device-pixel grid (≤ half a device
742 // pixel, imperceptible) so tile blits land on integral pixels and
743 // the tile and direct paths share one sub-pixel phase.
744 let q = pxPerSecond * Double(renderScale)
745 originSecond = (originSecond * q).rounded() / q
746 }
747 // Route the frame: warm frames (rest, pan, playback) blit the tile
748 // cache; cold frames — a zoom in flight (every tile invalid), a
749 // teleport jump (a whole viewport of new slices), or a live edit
750 // gesture (model changes per event) — rasterize all lanes in parallel
751 // instead, and the cache refills once things settle.
752 let editing = store.gestureBaseModel != nil || drag.mode == .box
753 let jumped = abs(originSecond - lastDrawOrigin) * pxPerSecond
754 > Double(Self.tileW)
755 let zoomed = pxPerSecond != lastDrawPps
756 lastDrawOrigin = originSecond
757 lastDrawPps = pxPerSecond
758 let tiled = !Self.tilesDisabled && !editing && !zoomed && !jumped
759 tileRenderBudget = Self.tileRendersPerFrame
760 if tiled {
761 let params = TileParams(
762 pps: pxPerSecond, scale: renderScale, light: Theme.light,
763 heights: Dictionary(uniqueKeysWithValues:
764 laneRows.map { ($0, laneHeight($0)) }),
765 showFilmstrips: session.showFilmstrips)
766 if params != tileParams {
767 flushTiles()
768 tileParams = params
769 }
770 } else if zoomed || jumped {
771 // The gesture invalidated the cache; warm it back up as soon as
772 // the stream of cold frames stops.
773 flushTiles()
774 tileParams = nil
775 scheduleTileFill()
776 }
777 let rows = laneRows
385778 DrawProf.t("lanes") {
386 for row in 0..<rows.count {
387 drawLane(row: row, ref: rows[row])
779 if tiled {
780 for row in 0..<rows.count {
781 drawLane(row: row, ref: rows[row], tiled: true)
782 }
783 } else {
784 drawLanesDirectParallel(rows: rows)
388785 }
389786 }
390 drawDragHintLanes()
391 drawFileDropPreview()
392 drawFusionBand()
787 DrawProf.t("hints") {
788 drawDragHintLanes()
789 drawFileDropPreview()
790 }
791 DrawProf.t("fusion") { drawFusionBand() }
393792 // Opaque header column so clips never show behind the buttons.
394793 Theme.timelineBg.setFill()
395794 NSRect(x: 0, y: rulerH, width: headerW, height: bounds.height - rulerH).fill()
......@@ -399,13 +798,16 @@ final class TimelineView: NSView {
399798 drawTrackHeader(row: row, ref: rows[row], lane: laneRect(row: row))
400799 }
401800 }
402 drawRuler()
403 drawInOut()
404 drawMarkers()
405 drawSnapIndicator()
406 drawBoxSelect()
407 drawPlayhead()
408 drawScrollbars()
801 DrawProf.t("ruler") { drawRuler() }
802 DrawProf.t("strip") { drawOptimizeStrip() }
803 DrawProf.t("overlays") {
804 drawInOut()
805 drawMarkers()
806 drawSnapIndicator()
807 drawBoxSelect()
808 drawPlayhead()
809 }
810 DrawProf.t("scrollbars") { drawScrollbars() }
409811 }
410812
411813 // MARK: - Scroll/zoom bars
......@@ -427,7 +829,8 @@ final class TimelineView: NSView {
427829 private func hDomain() -> (lo: Double, hi: Double) {
428830 let viewSec = Double(bounds.width - headerW) / pxPerSecond
429831 let lo = min(0, originSecond)
430 let hi = max(project.timelineDuration + 10, originSecond + viewSec)
832 rebuildSceneIfNeeded()
833 let hi = max(cachedTimelineDuration + 10, originSecond + viewSec)
431834 return (lo, hi)
432835 }
433836
......@@ -491,33 +894,30 @@ final class TimelineView: NSView {
491894 return nil
492895 }
493896
494 private func drawLane(row: Int, ref: TrackRef) {
897 private func drawLane(row: Int, ref: TrackRef, tiled: Bool) {
495898 let lane = laneRect(row: row)
496899 guard lane.minY < bounds.maxY, lane.maxY > lanesTop else { return }
497900 (ref == .storyboard ? Theme.storyboardLaneBg : Theme.laneBg).setFill()
498901 NSBezierPath(roundedRect: lane, xRadius: 4, yRadius: 4).fill()
499902
500 let overlaps = overlapsByLane[ref] ?? []
501 let overlappingIds = Set(overlaps.flatMap { [$0.a.id, $0.b.id] })
502
503 // Horizontal culling: clips are sorted by start, so once one starts past
504 // the right edge every later clip does too — stop. Clips ending before
505 // the left edge are skipped. Keeps the loop proportional to what's
506 // on-screen, not to the whole (possibly huge) lane.
507 let leftSec = originSecond
508 let rightSec = originSecond + Double(bounds.width - headerW) / pxPerSecond
509 let colors = laneColors(ref)
510 for clip in clipsByLane[ref] ?? [] {
511 if clip.start > rightSec { break }
512 if clip.end < leftSec { continue }
513 drawClip(clip, row: row, ref: ref, colors: colors,
514 overlapping: overlappingIds.contains(clip.id))
903 if tiled {
904 blitLaneTiles(ref: ref, lane: lane)
905 } else {
906 drawLaneClips(ref: ref, lane: lane,
907 cullX0: headerW, cullX1: bounds.width)
515908 }
909 drawLaneDecorations(ref: ref, lane: lane)
910 }
911
912 /// Pinned labels + overlap bands — main-thread, over blits or direct clips.
913 private func drawLaneDecorations(ref: TrackRef, lane: NSRect) {
914 drawPinnedLabels(ref: ref, lane: lane)
516915
517916 // Bright red overlap ranges on top of the clip bodies.
518 for o in overlaps {
917 for o in overlapsByLane[ref] ?? [] {
519918 let r = NSRect(x: xFor(o.start), y: lane.minY + 1,
520919 width: max(2, xFor(o.end) - xFor(o.start)), height: lane.height - 2)
920 guard r.maxX > headerW, r.minX < bounds.width else { continue }
521921 NSColor.systemRed.withAlphaComponent(0.40).setFill()
522922 r.fill()
523923 NSColor.systemRed.setStroke()
......@@ -527,6 +927,123 @@ final class TimelineView: NSView {
527927 }
528928 }
529929
930 // MARK: - Parallel direct rendering (cold frames)
931
932 /// Persistent per-lane raster buffers for cold frames; recreated on size/
933 /// scale change. ~7 MB per lane at 2× on a wide window — the price of
934 /// rasterizing a zoom gesture on every core instead of one.
935 private var laneBufs: [TrackRef: CGContext] = [:]
936
937 /// A cold frame (zoom in flight, teleport jump, live edit gesture) must
938 /// re-render every visible lane from scratch — but lanes are independent,
939 /// so rasterize them CONCURRENTLY into per-lane buffers with the exact
940 /// same `drawLaneClips` code and composite on main. Same rasterizer, same
941 /// pixels; wall-clock is the slowest lane, not the sum. The storyboard
942 /// lane draws serially on main (BoardStore's raster cache isn't locked).
943 private func drawLanesDirectParallel(rows: [TrackRef]) {
944 var work: [(ref: TrackRef, lane: NSRect)] = []
945 for (row, ref) in rows.enumerated() {
946 let lane = laneRect(row: row)
947 guard lane.minY < bounds.maxY, lane.maxY > lanesTop else { continue }
948 (ref == .storyboard ? Theme.storyboardLaneBg : Theme.laneBg).setFill()
949 NSBezierPath(roundedRect: lane, xRadius: 4, yRadius: 4).fill()
950 work.append((ref, lane))
951 }
952 guard !work.isEmpty, let mainCG = NSGraphicsContext.current?.cgContext
953 else { return }
954
955 let scale = renderScale
956 let pxW = Int((bounds.width * scale).rounded())
957 let space = window?.colorSpace?.cgColorSpace
958 ?? CGColorSpace(name: CGColorSpace.sRGB)!
959 let ctxs: [CGContext?] = work.map { item in
960 if item.ref == .storyboard { return nil }
961 let pxH = Int((item.lane.height * scale).rounded())
962 if let c = laneBufs[item.ref], c.width == pxW, c.height == pxH {
963 return c
964 }
965 let c = CGContext(
966 data: nil, width: pxW, height: pxH, bitsPerComponent: 8,
967 bytesPerRow: 0, space: space,
968 bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue
969 | CGBitmapInfo.byteOrder32Little.rawValue)
970 laneBufs[item.ref] = c
971 return c
972 }
973
974 DispatchQueue.concurrentPerform(iterations: work.count) { i in
975 guard let cg = ctxs[i] else { return } // storyboard → main below
976 let (ref, lane) = work[i]
977 autoreleasepool {
978 cg.clear(CGRect(x: 0, y: 0, width: CGFloat(cg.width),
979 height: CGFloat(cg.height)))
980 cg.saveGState()
981 cg.scaleBy(x: scale, y: scale)
982 cg.translateBy(x: 0, y: lane.height)
983 cg.scaleBy(x: 1, y: -1)
984 cg.translateBy(x: 0, y: -lane.minY)
985 // NSGraphicsContext.current is thread-local — but
986 // concurrentPerform runs one of these iterations ON the
987 // calling (main) thread, so the view's own context must be
988 // restored, not nilled.
989 let prev = NSGraphicsContext.current
990 NSGraphicsContext.current = NSGraphicsContext(cgContext: cg, flipped: true)
991 drawLaneClips(ref: ref, lane: lane,
992 cullX0: headerW, cullX1: bounds.width)
993 NSGraphicsContext.current = prev
994 cg.restoreGState()
995 }
996 }
997
998 for (i, item) in work.enumerated() {
999 if let cg = ctxs[i] {
1000 if let img = cg.makeImage() {
1001 mainCG.saveGState()
1002 mainCG.translateBy(x: 0, y: item.lane.maxY)
1003 mainCG.scaleBy(x: 1, y: -1)
1004 mainCG.draw(img, in: CGRect(x: 0, y: 0, width: bounds.width,
1005 height: item.lane.height))
1006 mainCG.restoreGState()
1007 }
1008 } else {
1009 drawLaneClips(ref: item.ref, lane: item.lane,
1010 cullX0: headerW, cullX1: bounds.width)
1011 }
1012 drawLaneDecorations(ref: item.ref, lane: item.lane)
1013 }
1014 }
1015
1016 /// One lane's clips into the current context, culled and clamped to the
1017 /// window [cullX0, cullX1] (view coordinates). The window is the view for
1018 /// direct drawing and the tile's span (± bleed) when rendering a tile —
1019 /// the drawing itself is identical, so the two paths are pixel-equivalent.
1020 private func drawLaneClips(ref: TrackRef, lane: NSRect,
1021 cullX0: CGFloat, cullX1: CGFloat) {
1022 let overlappingIds = overlapIdsByLane[ref] ?? []
1023 // Bleed: fade handles / shot dividers / borders paint a few points
1024 // past a clip's rect, so a clip just outside the window still owns
1025 // pixels inside it.
1026 let bleed: CGFloat = 8
1027 let leftSec = secondsFor(cullX0 - bleed)
1028 let rightSec = secondsFor(cullX1 + bleed)
1029 let colors = laneColors(ref)
1030 let clips = clipsByLane[ref] ?? []
1031 // A storyboard panel's left divider depends on whether an earlier panel
1032 // exists on the lane; the array is start-sorted, so any predecessor
1033 // with a strictly smaller start means "has previous".
1034 let range = visibleIndexRange(clips, lane: ref, left: leftSec, right: rightSec)
1035 for i in range {
1036 let clip = clips[i]
1037 if clip.end < leftSec { continue }
1038 let hasPrevPanel = ref == .storyboard && i > 0
1039 && clips[0].start < clip.start - 1e-6
1040 drawClip(clip, lane: lane, ref: ref, colors: colors,
1041 overlapping: overlappingIds.contains(clip.id),
1042 hasPrevPanel: hasPrevPanel,
1043 cullX0: cullX0 - bleed, cullX1: cullX1 + bleed)
1044 }
1045 }
1046
5301047 /// Circular SF-Symbol button in the header column (hide preview / focus).
5311048 private func drawHeaderButton(_ symbolName: String, centerY: CGFloat, on: Bool) {
5321049 drawHeaderButton(symbolName, in: NSRect(x: headerW / 2 - 8.5, y: centerY - 8.5,
......@@ -637,7 +1154,7 @@ final class TimelineView: NSView {
6371154
6381155 private func drawFusionHeader() {
6391156 guard fusionBandH > 0 else { return }
640 let band = NSRect(x: 0, y: rulerH, width: headerW, height: fusionBandH)
1157 let band = NSRect(x: 0, y: fusionTop, width: headerW, height: fusionBandH)
6411158 FusionComps.yellow.withAlphaComponent(session.fusionHidden ? 0.25 : 0.6).setFill()
6421159 NSBezierPath(roundedRect: band.insetBy(dx: 2, dy: 2), xRadius: 3, yRadius: 3).fill()
6431160 let attrs: [NSAttributedString.Key: Any] = [
......@@ -656,7 +1173,7 @@ final class TimelineView: NSView {
6561173 // become tracks. No resident placeholder lane. (Dropped FILES get their
6571174 // own landing preview in drawFileDropPreview.)
6581175 var rows: [Int] = []
659 let count = project.laneRefs.count
1176 let count = laneRows.count
6601177 if drag.mode == .move, let row = dragHintRow, row >= count {
6611178 rows = Array(count...row)
6621179 }
......@@ -672,46 +1189,113 @@ final class TimelineView: NSView {
6721189 }
6731190 }
6741191
675 private func drawClip(_ clip: Clip, row: Int, ref: TrackRef, colors: LaneColors,
676 overlapping: Bool) {
677 let rect = clipRect(clip, row: row)
678 guard rect.maxX > headerW, rect.minX < bounds.width else { return }
1192 private func drawClip(_ clip: Clip, lane: NSRect, ref: TrackRef, colors: LaneColors,
1193 overlapping: Bool, hasPrevPanel: Bool,
1194 cullX0: CGFloat, cullX1: CGFloat) {
1195 let x0 = xFor(clip.start), x1 = xFor(clip.end)
1196 let rect = NSRect(x: x0, y: lane.minY, width: max(2, x1 - x0), height: lane.height)
1197 guard rect.maxX > cullX0, rect.minX < cullX1 else { return }
6791198 let media = clip.mediaId.flatMap { mediaById[$0] }
6801199 let color = colors.base
6811200 let selected = store.selection.contains(clip.id)
1201 let linkedSel = !selected && linkedSelectionCache.contains(clip.id)
6821202
6831203 // Storyboard panels tile edge-to-edge (they're gapless) so the track
6841204 // reads as one continuous filmstrip — square corners, no per-panel
6851205 // card, dividers drawn between shots below.
6861206 let storyboard = clip.kind == .storyboard
1207
1208 func bodyFillColor() -> NSColor {
1209 switch clip.kind {
1210 case .storyboard: NSColor(calibratedWhite: 0.88, alpha: 1)
1211 case .audio: colors.audioBody
1212 case .video: media == nil ? NSColor(calibratedWhite: 0.25, alpha: 1)
1213 : colors.videoBody
1214 }
1215 }
1216
1217 // ---- Fast path: a clip too narrow to resolve any detail. ----
1218 // Below `lodMinWidth` the rounded corners, strip text, badges and
1219 // filmstrip/waveform columns are physically invisible, so flat rect
1220 // fills produce the same picture without the bezier/clip-state chrome
1221 // that made dense zooms cost hundreds of ms. Everything that still
1222 // reads at this size is kept: body + strip colors, the audio center
1223 // line and fade handles (the texture of dense audio lanes), status
1224 // tint/borders, storyboard dividers.
1225 if rect.width < Self.lodMinWidth {
1226 bodyFillColor().setFill()
1227 rect.fill()
1228 colors.strip.setFill()
1229 NSRect(x: rect.minX, y: rect.minY, width: rect.width, height: 13).fill()
1230 if clip.kind == .audio {
1231 color.withAlphaComponent(0.35).setFill()
1232 NSRect(x: rect.minX, y: rect.midY + 6, width: rect.width, height: 1).fill()
1233 // Fade-handle slivers: the full path clips the 7 px handle
1234 // ovals to the clip body, so at this width only a couple of
1235 // white columns survive — the speckle texture of a dense audio
1236 // lane. Fill the same intersection directly.
1237 (selected ? NSColor.white : NSColor(calibratedWhite: 0.85, alpha: 0.9)).setFill()
1238 let hy = rect.minY + 13.5
1239 for x in [xFor(clip.start + clip.fadeIn), xFor(clip.end - clip.fadeOut)] {
1240 let sliver = NSRect(x: x - 3.5, y: hy, width: 7, height: 7)
1241 .intersection(rect)
1242 if !sliver.isEmpty { sliver.fill() }
1243 }
1244 }
1245 // Status tint only: at this width the border stroke's inset path is
1246 // degenerate (sub-zero width) and renders nothing today, and the
1247 // red overlap band is painted at lane level. The tint is what reads.
1248 if selected || linkedSel {
1249 (selected ? NSColor.controlAccentColor : NSColor.systemCyan)
1250 .withAlphaComponent(selected ? 0.34 : 0.20).setFill()
1251 rect.fill()
1252 }
1253 if storyboard { drawShotDivider(clip, rect: rect, hasPrev: hasPrevPanel) }
1254 return
1255 }
1256
1257 // A zoomed-in clip's rect can be literally millions of points wide;
1258 // building paths and image draws at that size costs real time even
1259 // though almost all of it is clipped away. Clamp the card geometry to
1260 // the view plus a margin that keeps the rounded corners and border
1261 // strokes of the clamped edges safely offscreen — the pixels inside
1262 // the view are identical. (The filmstrip keeps the TRUE rect: its
1263 // thumbnail tiling is phase-anchored to the clip's real left edge, and
1264 // it already culls to the visible span internally.)
1265 let clampMargin: CGFloat = 12
1266 let cardRect: NSRect = {
1267 let cx0 = max(rect.minX, cullX0 - clampMargin)
1268 let cx1 = min(rect.maxX, cullX1 + clampMargin)
1269 return NSRect(x: cx0, y: rect.minY, width: cx1 - cx0, height: rect.height)
1270 }()
1271
6871272 let bodyRect = storyboard
688 ? NSRect(x: rect.minX, y: rect.minY + 0.5, width: rect.width, height: rect.height - 1)
689 : rect.insetBy(dx: 0.5, dy: 0.5)
1273 ? NSRect(x: cardRect.minX, y: cardRect.minY + 0.5,
1274 width: cardRect.width, height: cardRect.height - 1)
1275 : cardRect.insetBy(dx: 0.5, dy: 0.5)
6901276 let bodyRadius: CGFloat = storyboard ? 0 : 3
691 let body = NSBezierPath(roundedRect: bodyRect, xRadius: bodyRadius, yRadius: bodyRadius)
692 switch clip.kind {
693 case .storyboard:
694 NSColor(calibratedWhite: 0.88, alpha: 1).setFill()
695 case .audio:
696 colors.audioBody.setFill()
697 case .video:
698 (media == nil ? NSColor(calibratedWhite: 0.25, alpha: 1) : colors.videoBody).setFill()
699 }
700 body.fill()
701
702 // Full detail (thumbnails, waveforms, labels) normally — including while
703 // scrolling. Only a very dense frame mid-scroll drops to bodies + strip +
704 // border (see `lightScroll`), and only until the pan settles.
705 let detail = !lightScroll
706
707 if detail {
1277 let body = DrawProf.t("clip.body") {
1278 let p = NSBezierPath(roundedRect: bodyRect, xRadius: bodyRadius,
1279 yRadius: bodyRadius)
1280 bodyFillColor().setFill()
1281 p.fill()
1282 return p
1283 }
1284
1285 DrawProf.t("clip.content") {
7081286 NSGraphicsContext.current?.saveGraphicsState()
7091287 body.addClip()
7101288 switch clip.kind {
7111289 case .video:
712 if let media, session.showFilmstrips { drawFilmstrip(clip, media: media, rect: rect) }
1290 if let media, session.showFilmstrips {
1291 drawFilmstrip(clip, media: media, rect: rect,
1292 cullX0: cullX0, cullX1: cullX1)
1293 }
7131294 case .audio:
714 if let media { drawWaveform(clip, media: media, rect: rect, color: color) }
1295 if let media {
1296 drawWaveform(clip, media: media, rect: rect, color: color,
1297 cullX0: cullX0, cullX1: cullX1)
1298 }
7151299 drawFades(clip, rect: rect, selected: selected)
7161300 case .storyboard:
7171301 drawBoardThumb(clip, rect: rect)
......@@ -719,23 +1303,22 @@ final class TimelineView: NSView {
7191303 NSGraphicsContext.current?.restoreGraphicsState()
7201304 }
7211305
722 let linkedSel = !selected && linkedSelectionCache.contains(clip.id)
723
7241306 // Title strip
725 var title = media?.displayName
726 ?? (clip.kind == .storyboard
727 ? (panelNamesCache[clip.id] ?? "Panel") : "missing media")
728 if clip.kind != .storyboard && media == nil { title = "⚠︎ " + title }
729 if clip.kind == .audio { title = "♪ " + title }
730 // The label is drawn only at rest and only when the clip is wide enough
731 // to read; a clip narrower than this shows no legible text anyway.
732 let showLabel = detail && rect.width >= 22
733 if detail { NSGraphicsContext.current?.saveGraphicsState(); body.addClip() }
1307 let title = clipTitle(clip, media: media)
1308 // The label is drawn only when the clip is wide enough to read; a clip
1309 // narrower than this shows no legible text anyway.
1310 let showLabel = rect.width >= 22
1311 DrawProf.t("clip.strip") {
1312 NSGraphicsContext.current?.saveGraphicsState()
1313 body.addClip()
7341314 colors.strip.setFill()
735 NSRect(x: rect.minX, y: rect.minY, width: rect.width, height: 13).fill()
1315 NSRect(x: cardRect.minX, y: cardRect.minY, width: cardRect.width, height: 13).fill()
7361316 if showLabel {
737 title.draw(at: NSPoint(x: max(rect.minX, headerW) + 5, y: rect.minY + 1),
738 withAttributes: Self.titleAttrs)
1317 // Unpinned: the label sits at the clip's true left edge. A clip
1318 // scrolled off the left edge gets its label re-pinned to the view
1319 // edge by `drawPinnedLabels` — an overlay pass, so the pinning
1320 // never gets baked into a cached tile.
1321 drawTitle(title, at: NSPoint(x: rect.minX + 5, y: rect.minY + 1))
7391322
7401323 var badgeX = rect.maxX - 16
7411324 if clip.speed != 1 {
......@@ -759,16 +1342,19 @@ final class TimelineView: NSView {
7591342 from: .zero, operation: .sourceOver, fraction: 0.7)
7601343 }
7611344 }
762 if detail { NSGraphicsContext.current?.restoreGraphicsState() }
1345 NSGraphicsContext.current?.restoreGraphicsState()
1346 }
7631347
1348 DrawProf.t("clip.chrome") {
7641349 // Selection reads as a full-card tint, not just an outline. Link-mates
7651350 // of the selection (they act selected) tint aqua.
7661351 if selected || linkedSel {
767 if detail { NSGraphicsContext.current?.saveGraphicsState(); body.addClip() }
1352 NSGraphicsContext.current?.saveGraphicsState()
1353 body.addClip()
7681354 (selected ? NSColor.controlAccentColor : NSColor.systemCyan)
7691355 .withAlphaComponent(selected ? 0.34 : 0.20).setFill()
770 (detail ? rect : bodyRect).fill()
771 if detail { NSGraphicsContext.current?.restoreGraphicsState() }
1356 cardRect.fill()
1357 NSGraphicsContext.current?.restoreGraphicsState()
7721358 }
7731359
7741360 // Border LAST, on top of the tint: overlap = red, selection = accent
......@@ -777,7 +1363,7 @@ final class TimelineView: NSView {
7771363 // outline (selected / linked / overlapping).
7781364 if !storyboard || selected || linkedSel || overlapping {
7791365 let radius: CGFloat = storyboard ? 0 : 3
780 let border = NSBezierPath(roundedRect: rect.insetBy(dx: 1.25, dy: 1.25),
1366 let border = NSBezierPath(roundedRect: cardRect.insetBy(dx: 1.25, dy: 1.25),
7811367 xRadius: radius, yRadius: radius)
7821368 border.lineWidth = selected ? 3.5 : linkedSel ? 3 : (overlapping ? 2 : 1.5)
7831369 (selected ? Theme.selection
......@@ -786,42 +1372,141 @@ final class TimelineView: NSView {
7861372 border.stroke()
7871373 }
7881374
789 // Shot divider: an opaque line sitting ON the boundary between two
790 // storyboard panels (they tile gaplessly). A new shot gets a bold
791 // orange bar; frames within a shot get a thin neutral line. The first
792 // panel of the track has no divider on its left.
793 if storyboard {
794 let hasPrev = project.clips.contains {
795 $0.id != clip.id && $0.kind == .storyboard
796 && $0.track == clip.track && $0.start < clip.start - 1e-6
1375 if storyboard { drawShotDivider(clip, rect: rect, hasPrev: hasPrevPanel) }
1376 }
1377 }
1378
1379 private func clipTitle(_ clip: Clip, media: MediaItem?) -> String {
1380 var title = media?.displayName
1381 ?? (clip.kind == .storyboard
1382 ? (panelNamesCache[clip.id] ?? "Panel") : "missing media")
1383 if clip.kind != .storyboard && media == nil { title = "⚠︎ " + title }
1384 if clip.kind == .audio { title = "♪ " + title }
1385 return title
1386 }
1387
1388 /// Re-pin the title of any clip whose left edge is scrolled offscreen: the
1389 /// label follows the view edge (as it always has), but the pinning is an
1390 /// overlay so it is never baked into a scene tile. The patch repaints the
1391 /// clip's own strip pixels under the new label position (covering the
1392 /// baked, unpinned label's tail), then restrokes the border segment it
1393 /// covered — output matches the old single-pass pinned render.
1394 private func drawPinnedLabels(ref: TrackRef, lane: NSRect) {
1395 let leftSec = secondsFor(headerW)
1396 let colors = laneColors(ref)
1397 let clips = clipsByLane[ref] ?? []
1398 // Only clips STRADDLING the left edge qualify; binary search the
1399 // candidate window instead of scanning every clip left of the view.
1400 for i in visibleIndexRange(clips, lane: ref, left: leftSec, right: leftSec) {
1401 let clip = clips[i]
1402 guard clip.start < leftSec, clip.end > leftSec else { continue }
1403 let x0 = xFor(clip.start), x1 = xFor(clip.end)
1404 let rect = NSRect(x: x0, y: lane.minY, width: max(2, x1 - x0),
1405 height: lane.height)
1406 guard rect.width >= 22 else { continue }
1407 let media = clip.mediaId.flatMap { mediaById[$0] }
1408 let title = clipTitle(clip, media: media)
1409 let selected = store.selection.contains(clip.id)
1410 let linkedSel = !selected && linkedSelectionCache.contains(clip.id)
1411 let overlapping = (overlapIdsByLane[ref] ?? []).contains(clip.id)
1412
1413 let labelW = title.size(withAttributes: Self.titleAttrs).width
1414 let patch = NSRect(x: headerW, y: rect.minY,
1415 width: min(labelW + 12, rect.maxX - headerW), height: 13)
1416 let storyboard = clip.kind == .storyboard
1417 let cx1 = min(rect.maxX, bounds.width + 12)
1418 let cardRect = NSRect(x: max(rect.minX, headerW - 12), y: rect.minY,
1419 width: cx1 - max(rect.minX, headerW - 12),
1420 height: rect.height)
1421 let bodyRect = storyboard
1422 ? NSRect(x: cardRect.minX, y: cardRect.minY + 0.5,
1423 width: cardRect.width, height: cardRect.height - 1)
1424 : cardRect.insetBy(dx: 0.5, dy: 0.5)
1425 let radius: CGFloat = storyboard ? 0 : 3
1426 let body = NSBezierPath(roundedRect: bodyRect, xRadius: radius, yRadius: radius)
1427
1428 NSGraphicsContext.current?.saveGraphicsState()
1429 NSRect(x: patch.minX, y: patch.minY, width: patch.width,
1430 height: patch.height).clip()
1431 body.addClip()
1432 // Rebuild the strip composite: body color under the 0.85-alpha strip.
1433 switch clip.kind {
1434 case .storyboard: NSColor(calibratedWhite: 0.88, alpha: 1).setFill()
1435 case .audio: colors.audioBody.setFill()
1436 case .video: (media == nil ? NSColor(calibratedWhite: 0.25, alpha: 1)
1437 : colors.videoBody).setFill()
7971438 }
798 if hasPrev {
799 if clip.newShot {
800 NSColor.systemOrange.setFill()
801 NSRect(x: rect.minX - 1.5, y: rect.minY, width: 3, height: rect.height).fill()
802 } else {
803 NSColor(calibratedWhite: Theme.light ? 0.45 : 0.30, alpha: 1).setFill()
804 NSRect(x: rect.minX - 0.5, y: rect.minY, width: 1, height: rect.height).fill()
805 }
1439 patch.fill()
1440 colors.strip.setFill()
1441 patch.fill()
1442 drawTitle(title, at: NSPoint(x: headerW + 5, y: rect.minY + 1))
1443 if selected || linkedSel {
1444 (selected ? NSColor.controlAccentColor : NSColor.systemCyan)
1445 .withAlphaComponent(selected ? 0.34 : 0.20).setFill()
1446 patch.fill()
1447 }
1448 NSGraphicsContext.current?.restoreGraphicsState()
1449
1450 // Restroke the border segment the patch painted over — clipped to
1451 // the patch only (drawClip strokes the border unclipped, so a
1452 // body clip here would shave its outer antialiasing).
1453 if !storyboard || selected || linkedSel || overlapping {
1454 NSGraphicsContext.current?.saveGraphicsState()
1455 patch.insetBy(dx: 0, dy: -1).clip()
1456 let border = NSBezierPath(
1457 roundedRect: cardRect.insetBy(dx: 1.25, dy: 1.25),
1458 xRadius: radius, yRadius: radius)
1459 border.lineWidth = selected ? 3.5 : linkedSel ? 3 : (overlapping ? 2 : 1.5)
1460 (selected ? Theme.selection
1461 : linkedSel ? NSColor.systemCyan
1462 : overlapping ? NSColor.systemRed : colors.base).setStroke()
1463 border.stroke()
1464 NSGraphicsContext.current?.restoreGraphicsState()
8061465 }
8071466 }
8081467 }
8091468
810 private func drawFilmstrip(_ clip: Clip, media: MediaItem, rect: NSRect) {
1469 /// Shot divider: an opaque line sitting ON the boundary between two
1470 /// storyboard panels (they tile gaplessly). A new shot gets a bold
1471 /// orange bar; frames within a shot get a thin neutral line. The first
1472 /// panel of the track has no divider on its left.
1473 private func drawShotDivider(_ clip: Clip, rect: NSRect, hasPrev: Bool) {
1474 guard hasPrev else { return }
1475 if clip.newShot {
1476 NSColor.systemOrange.setFill()
1477 NSRect(x: rect.minX - 1.5, y: rect.minY, width: 3, height: rect.height).fill()
1478 } else {
1479 NSColor(calibratedWhite: Theme.light ? 0.45 : 0.30, alpha: 1).setFill()
1480 NSRect(x: rect.minX - 0.5, y: rect.minY, width: 1, height: rect.height).fill()
1481 }
1482 }
1483
1484 private func drawFilmstrip(_ clip: Clip, media: MediaItem, rect: NSRect,
1485 cullX0: CGFloat, cullX1: CGFloat) {
8111486 let thumbH = rect.height - 14
8121487 guard thumbH > 6 else { return }
8131488 let mediaAspect = media.width > 0 && media.height > 0
8141489 ? CGFloat(media.width) / CGFloat(media.height) : 16.0 / 9.0
8151490 let thumbW = thumbH * mediaAspect
816 let visX0 = max(rect.minX, headerW), visX1 = min(rect.maxX, bounds.width)
1491 // Thumb tiling stays phase-anchored to the clip's TRUE left edge (so
1492 // thumbnails never slide as the window changes); the window only culls.
1493 let visX0 = max(rect.minX, cullX0), visX1 = min(rect.maxX, cullX1)
1494 guard let cg = NSGraphicsContext.current?.cgContext else { return }
8171495 var x = rect.minX + floor((visX0 - rect.minX) / thumbW) * thumbW
8181496 while x < visX1 {
8191497 let tlSec = secondsFor(x + thumbW / 2)
8201498 let srcSec = clip.sourceTime(at: tlSec)
8211499 if let img = MediaPipeline.shared.filmstripImage(for: media, at: max(0, srcSec)) {
8221500 if DrawProf.on { DrawProf.thumbHits += 1 }
823 img.draw(in: NSRect(x: x, y: rect.minY + 14, width: thumbW, height: thumbH),
824 from: .zero, operation: .sourceOver, fraction: 0.9)
1501 // CGContext blit with a local flip (CGImages draw bottom-up;
1502 // the view is flipped). The images are pre-converted to the
1503 // display's format, so this is a plain copy.
1504 cg.saveGState()
1505 cg.setAlpha(0.9)
1506 cg.translateBy(x: x, y: rect.minY + 14 + thumbH)
1507 cg.scaleBy(x: 1, y: -1)
1508 cg.draw(img, in: CGRect(x: 0, y: 0, width: thumbW, height: thumbH))
1509 cg.restoreGState()
8251510 }
8261511 x += thumbW
8271512 }
......@@ -829,20 +1514,36 @@ final class TimelineView: NSView {
8291514 rect.fill()
8301515 }
8311516
832 private func drawWaveform(_ clip: Clip, media: MediaItem, rect: NSRect, color: NSColor) {
1517 private func drawWaveform(_ clip: Clip, media: MediaItem, rect: NSRect, color: NSColor,
1518 cullX0: CGFloat, cullX1: CGFloat) {
8331519 // Center line
8341520 color.withAlphaComponent(0.35).setFill()
8351521 NSRect(x: rect.minX, y: rect.midY + 6, width: rect.width, height: 1).fill()
8361522 guard let img = MediaPipeline.shared.waveformImage(for: media), media.duration > 0
8371523 else { return }
838 let imgW = img.size.width
1524 // Draw only the visible span — a zoomed-in clip's full rect can be
1525 // millions of points wide, and image draws at that size are slow even
1526 // though it's all clipped away. CGImage has no source-rect draw, so
1527 // map the FULL image through the source→dest transform and clip to
1528 // the visible span: pixel-identical to a fractional source rect
1529 // (cropping(to:) would round to whole source pixels), and Quartz only
1530 // rasterizes the clipped part.
1531 let visX0 = max(rect.minX, cullX0 - 2), visX1 = min(rect.maxX, cullX1 + 2)
1532 guard visX1 > visX0, let cg = NSGraphicsContext.current?.cgContext else { return }
1533 let imgW = CGFloat(img.width)
8391534 let fromX = CGFloat(clip.srcIn / media.duration) * imgW
840 let fromW = CGFloat(clip.duration / media.duration) * imgW
1535 let fromW = max(1, CGFloat(clip.duration / media.duration) * imgW)
8411536 let dest = NSRect(x: rect.minX, y: rect.minY + 14,
8421537 width: rect.width, height: rect.height - 16)
843 img.draw(in: dest, from: NSRect(x: fromX, y: 0, width: max(1, fromW),
844 height: img.size.height),
845 operation: .sourceOver, fraction: 0.85)
1538 let scaleX = dest.width / fromW
1539 cg.saveGState()
1540 cg.setAlpha(0.85)
1541 cg.clip(to: CGRect(x: visX0, y: dest.minY, width: visX1 - visX0,
1542 height: dest.height))
1543 cg.translateBy(x: dest.minX - fromX * scaleX, y: dest.maxY)
1544 cg.scaleBy(x: 1, y: -1)
1545 cg.draw(img, in: CGRect(x: 0, y: 0, width: imgW * scaleX, height: dest.height))
1546 cg.restoreGState()
8461547 }
8471548
8481549 private func drawFades(_ clip: Clip, rect: NSRect, selected: Bool) {
......@@ -867,7 +1568,16 @@ final class TimelineView: NSView {
8671568 let xOut = xFor(clip.end - clip.fadeOut)
8681569 if clip.fadeIn > 0.001 { fadeShape(from: xFor(clip.start), to: xIn, leading: true) }
8691570 if clip.fadeOut > 0.001 { fadeShape(from: xFor(clip.end), to: xOut, leading: false) }
870 // Handles (always visible so fades stay discoverable).
1571 drawFadeHandles(clip, rect: rect, selected: selected)
1572 }
1573
1574 /// Fade handles (always visible so fades stay discoverable). Split out of
1575 /// `drawFades` because narrow (LOD) clips draw the handles — they're the
1576 /// speckled texture of a dense audio lane — without the fade shapes.
1577 private func drawFadeHandles(_ clip: Clip, rect: NSRect, selected: Bool) {
1578 let top = rect.minY + 13
1579 let xIn = xFor(clip.start + clip.fadeIn)
1580 let xOut = xFor(clip.end - clip.fadeOut)
8711581 for x in [xIn, xOut] {
8721582 let r = NSRect(x: x - 3.5, y: top - 3.5 + 4, width: 7, height: 7)
8731583 (selected ? NSColor.white : NSColor(calibratedWhite: 0.85, alpha: 0.9)).setFill()
......@@ -891,8 +1601,9 @@ final class TimelineView: NSView {
8911601 x += w + 2
8921602 break // one panel image; boards are one still, no need to tile
8931603 }
894 NSImage(systemSymbolName: "pencil.and.outline", accessibilityDescription: nil)?
895 .tinted(NSColor(calibratedWhite: 0.2, alpha: 1))
1604 Self.bakedSymbol("pencil.and.outline",
1605 tint: NSColor(calibratedWhite: 0.2, alpha: 1),
1606 key: "board.pencil")?
8961607 .draw(in: NSRect(x: rect.minX + 4, y: rect.minY + 16, width: 11, height: 11),
8971608 from: .zero, operation: .sourceOver, fraction: 0.9)
8981609 }
......@@ -901,7 +1612,7 @@ final class TimelineView: NSView {
9011612
9021613 private func drawFusionBand() {
9031614 guard fusionBandH > 0 else { return }
904 let band = NSRect(x: 0, y: rulerH, width: bounds.width, height: fusionBandH)
1615 let band = NSRect(x: 0, y: fusionTop, width: bounds.width, height: fusionBandH)
9051616 NSColor(calibratedRed: 0.16, green: 0.14, blue: 0.05, alpha: 1).setFill()
9061617 band.fill()
9071618 FusionComps.yellow.withAlphaComponent(0.5).setFill()
......@@ -943,10 +1654,10 @@ final class TimelineView: NSView {
9431654 }
9441655
9451656 private func compAt(point: NSPoint) -> FusionComp? {
946 guard fusionBandH > 0, point.y > rulerH, point.y < rulerH + fusionBandH
1657 guard fusionBandH > 0, point.y > fusionTop, point.y < fusionTop + fusionBandH
9471658 else { return nil }
9481659 let fps = project.fps
949 let band = NSRect(x: 0, y: rulerH, width: bounds.width, height: fusionBandH)
1660 let band = NSRect(x: 0, y: fusionTop, width: bounds.width, height: fusionBandH)
9501661 for (comp, lane, lanes) in comps.stacked() {
9511662 let x0 = xFor(comp.startSeconds(fps: fps))
9521663 let x1 = xFor(comp.endSeconds(fps: fps))
......@@ -958,6 +1669,109 @@ final class TimelineView: NSView {
9581669 return nil
9591670 }
9601671
1672 // MARK: - Optimization strip
1673
1674 /// Per-column optimization state for the thin band under the ruler:
1675 /// GREEN — every proxy chunk the visible tracks need at that time is
1676 /// built at the preview-quality target; DIM GREEN — built but below
1677 /// target (usable now, upgrade queued); YELLOW — building right now (or
1678 /// a rescue slice is standing in); RED — no proxy on disk (never built,
1679 /// evicted, or failed). Aggregated worst-wins across visible video
1680 /// tracks, so one glance says whether playing here will be instant.
1681 ///
1682 /// The runs are cached — recomputed only when the view moves (origin/
1683 /// zoom/width) or something actually changed (.projectChanged /
1684 /// .mediaStatusChanged clear the key) — so playback frames just re-fill
1685 /// a few dozen rects.
1686 private var stripRuns: [(x: CGFloat, w: CGFloat, color: NSColor)] = []
1687 private var stripKey: (origin: Double, pps: Double, width: CGFloat)?
1688
1689 private func rebuildStripIfNeeded() {
1690 if let k = stripKey, k.origin == originSecond, k.pps == pxPerSecond,
1691 k.width == bounds.width { return }
1692 stripKey = (originSecond, pxPerSecond, bounds.width)
1693 stripRuns.removeAll(keepingCapacity: true)
1694 rebuildSceneIfNeeded()
1695
1696 let colW: CGFloat = 2
1697 let ncols = Int((bounds.width - headerW) / colW) + 1
1698 guard ncols > 0 else { return }
1699 var vals = [UInt8](repeating: .max, count: ncols)
1700 let leftSec = secondsFor(headerW), rightSec = secondsFor(bounds.width)
1701 var snaps: [String: ChunkManager.StripSnapshot?] = [:]
1702
1703 func mark(_ lo: Double, _ hi: Double, _ v: UInt8) {
1704 let c0 = max(0, Int((xFor(lo) - headerW) / colW))
1705 let c1 = min(ncols - 1, Int((xFor(hi) - headerW) / colW))
1706 guard c1 >= c0 else { return }
1707 for c in c0...c1 where vals[c] > v { vals[c] = v }
1708 }
1709
1710 for ref in laneRows {
1711 guard ref.videoIndex != nil, !session.hiddenTracks.contains(ref) else { continue }
1712 let clips = clipsByLane[ref] ?? []
1713 for i in visibleIndexRange(clips, lane: ref, left: leftSec, right: rightSec) {
1714 let clip = clips[i]
1715 guard clip.kind == .video,
1716 let media = project.media(clip.mediaId), !media.isAudio,
1717 media.duration > 0 else { continue }
1718 let a = max(clip.start, leftSec), b = min(clip.end, rightSec)
1719 guard b > a else { continue }
1720 let snap: ChunkManager.StripSnapshot?
1721 if let cached = snaps[media.cacheKey] { snap = cached }
1722 else { snap = ctx.chunks.stripSnapshot(media: media); snaps[media.cacheKey] = snap }
1723 guard let snap else { mark(a, b, 0); continue } // unscanned: unknown
1724 if snap.fullProxy { mark(a, b, 3); continue } // legacy whole-file proxy
1725 let srcA = clip.srcIn + (a - clip.start) * clip.speed
1726 let srcB = clip.srcIn + (b - clip.start) * clip.speed
1727 let ci = min(snap.n - 1, ChunkManager.chunkIndex(forSource: min(srcA, srcB)))
1728 let cj = min(snap.n - 1, ChunkManager.chunkIndex(forSource: max(srcA, srcB) - 1e-6))
1729 for idx in ci...max(ci, cj) {
1730 let v: UInt8
1731 if let w = snap.built[idx] {
1732 v = w >= snap.target ? 3 : 2
1733 } else if snap.inFlight.contains(idx) || snap.partial.contains(idx) {
1734 v = 1
1735 } else {
1736 v = 0 // missing, evicted, or failed
1737 }
1738 let t0 = clip.start
1739 + (Double(idx) * ChunkManager.chunkSeconds - clip.srcIn) / max(1e-4, clip.speed)
1740 let t1 = clip.start
1741 + (Double(idx + 1) * ChunkManager.chunkSeconds - clip.srcIn) / max(1e-4, clip.speed)
1742 mark(max(min(t0, t1), a), min(max(t0, t1), b), v)
1743 }
1744 }
1745 }
1746
1747 // Compress equal-valued columns into fill runs.
1748 let palette = [Theme.stripMissing, Theme.stripBuilding,
1749 Theme.stripUsable, Theme.stripFull]
1750 var c = 0
1751 while c < ncols {
1752 let v = vals[c]
1753 var e = c + 1
1754 while e < ncols, vals[e] == v { e += 1 }
1755 if v != .max {
1756 stripRuns.append((x: headerW + CGFloat(c) * colW,
1757 w: CGFloat(e - c) * colW,
1758 color: palette[Int(v)]))
1759 }
1760 c = e
1761 }
1762 }
1763
1764 private func drawOptimizeStrip() {
1765 Theme.timelineBg.setFill()
1766 NSRect(x: 0, y: rulerH, width: bounds.width, height: stripH).fill()
1767 rebuildStripIfNeeded()
1768 let y = rulerH + 1
1769 for run in stripRuns {
1770 run.color.setFill()
1771 NSRect(x: run.x, y: y, width: run.w, height: stripH - 2).fill()
1772 }
1773 }
1774
9611775 // MARK: - Ruler / playhead / indicators
9621776
9631777 private func drawRuler() {
......@@ -1164,7 +1978,7 @@ final class TimelineView: NSView {
11641978 // MARK: - Mouse editing
11651979
11661980 private enum DragMode {
1167 case none, scrub, move, trimIn, trimOut, rippleOut, slip,
1981 case none, scrub, move, trimIn, trimOut, rippleOut, rippleIn, slip,
11681982 stretchIn, stretchOut, fadeIn, fadeOut, box, resizeTrack,
11691983 hBarPan, hBarLeft, hBarRight, vBarPan, vBarTop, vBarBottom
11701984 }
......@@ -1235,7 +2049,7 @@ final class TimelineView: NSView {
12352049 return
12362050 }
12372051
1238 if p.y < rulerH {
2052 if p.y < rulerH + stripH { // ruler + optimization strip both scrub
12392053 drag.mode = .scrub
12402054 playback.setRate(0)
12412055 playback.seek(to: max(0, quantize(secondsFor(p.x))))
......@@ -1243,8 +2057,8 @@ final class TimelineView: NSView {
12432057 }
12442058
12452059 // Fusion band header: hide preview (top) / focus (bottom)
1246 if fusionBandH > 0, p.x < headerW, p.y > rulerH, p.y < lanesTop {
1247 if p.y < rulerH + fusionBandH * 0.55 { session.fusionHidden.toggle() }
2060 if fusionBandH > 0, p.x < headerW, p.y > fusionTop, p.y < lanesTop {
2061 if p.y < fusionTop + fusionBandH * 0.55 { session.fusionHidden.toggle() }
12482062 else { session.fusionFocus.toggle() }
12492063 needsDisplay = true
12502064 return
......@@ -1348,13 +2162,17 @@ final class TimelineView: NSView {
13482162 if drag.mode == .none {
13492163 let stretch = event.modifierFlags.contains(.command) && clip.kind == .video
13502164 let opt = event.modifierFlags.contains(.option)
1351 if opt, rect.maxX - p.x < edge {
1352 drag.mode = .rippleOut // ⌥-drag out edge: push everything after
2165 let onIn = p.x - rect.minX < edge
2166 let onOut = rect.maxX - p.x < edge
2167 if opt, onOut {
2168 drag.mode = .rippleOut // ⌥-drag out edge: ripple the tail away
2169 } else if opt, onIn, clip.kind != .storyboard {
2170 drag.mode = .rippleIn // ⌥-drag in edge: ripple-trim the head
13532171 } else if opt || session.mainTool == .slide {
1354 drag.mode = .slip
1355 } else if p.x - rect.minX < edge {
2172 drag.mode = .slip // ⌥-drag body: slip source under the clip
2173 } else if onIn {
13562174 drag.mode = stretch ? .stretchIn : .trimIn
1357 } else if rect.maxX - p.x < edge {
2175 } else if onOut {
13582176 drag.mode = stretch ? .stretchOut : .trimOut
13592177 } else {
13602178 drag.mode = .move
......@@ -1395,7 +2213,7 @@ final class TimelineView: NSView {
13952213
13962214 // Dragging against the view edges pans the timeline (there's no
13972215 // enclosing scroll view, so the playhead could never leave the screen).
1398 if [.scrub, .move, .trimIn, .trimOut, .rippleOut, .slip,
2216 if [.scrub, .move, .trimIn, .trimOut, .rippleOut, .rippleIn, .slip,
13992217 .stretchIn, .stretchOut, .fadeIn, .fadeOut].contains(drag.mode) {
14002218 if p.x > bounds.width - 30 {
14012219 originSecond += Double(p.x - (bounds.width - 30)) * 0.12 / pxPerSecond
......@@ -1413,6 +2231,7 @@ final class TimelineView: NSView {
14132231 case .trimIn: dragTrimIn(dSec: dSec)
14142232 case .trimOut: dragTrimOut(dSec: dSec)
14152233 case .rippleOut: dragRippleOut(dSec: dSec)
2234 case .rippleIn: dragRippleIn(dSec: dSec)
14162235 case .slip: dragSlip(dSec: dSec)
14172236 case .stretchIn: dragStretch(dSec: dSec, fromStart: true)
14182237 case .stretchOut: dragStretch(dSec: dSec, fromStart: false)
......@@ -1458,8 +2277,9 @@ final class TimelineView: NSView {
14582277 case .box:
14592278 let r = boxRect()
14602279 var hit = drag.baseSelection
1461 for (row, ref) in project.laneRefs.enumerated() {
1462 for clip in project.clips(on: ref)
2280 rebuildSceneIfNeeded()
2281 for (row, ref) in laneRows.enumerated() {
2282 for clip in clipsByLane[ref] ?? []
14632283 where clipRect(clip, row: row).intersects(r) {
14642284 hit.insert(clip.id)
14652285 }
......@@ -1472,12 +2292,6 @@ final class TimelineView: NSView {
14722292 session.trackHeights[ref] = min(4, max(0.35, factor))
14732293 }
14742294 }
1475 // Dragging the floating scrollbars is a scroll/zoom — simplify while it
1476 // moves, like the wheel and pinch paths.
1477 if [.hBarPan, .hBarLeft, .hBarRight,
1478 .vBarPan, .vBarTop, .vBarBottom].contains(drag.mode) {
1479 noteScrolling()
1480 }
14812295 drag.moved = true
14822296 autoscroll(with: event)
14832297 needsDisplay = true
......@@ -1485,7 +2299,7 @@ final class TimelineView: NSView {
14852299
14862300 override func mouseUp(with event: NSEvent) {
14872301 switch drag.mode {
1488 case .move, .trimIn, .trimOut, .rippleOut, .slip,
2302 case .move, .trimIn, .trimOut, .rippleOut, .rippleIn, .slip,
14892303 .stretchIn, .stretchOut, .fadeIn, .fadeOut:
14902304 store.endGesture()
14912305 default:
......@@ -1518,7 +2332,6 @@ final class TimelineView: NSView {
15182332 if maxScrollY > 0 {
15192333 scrollY = min(max(0, panDrag.origScrollY - (p.y - panDrag.start.y)), maxScrollY)
15202334 }
1521 noteScrolling()
15222335 needsDisplay = true
15232336 }
15242337
......@@ -1532,14 +2345,11 @@ final class TimelineView: NSView {
15322345 // Vertical retracking only for a lone unlinked clip.
15332346 let multi = drag.origSelection.count > 1 || store.selection.count > 1
15342347
1535 var delta = dSec
1536 if let adj = snapAdjust(start: orig.start + dSec, duration: orig.duration,
1537 excluding: Set(drag.origSelection.keys)) {
1538 delta += adj.adjust
1539 activeSnapTarget = adj.target
1540 }
1541 // Frame-quantize the moved edge, clamp to t >= 0 for all moved clips.
1542 delta = quantize(orig.start + delta) - orig.start
2348 // Snap+quantize the moved start edge, then clamp to t >= 0 for all moved clips.
2349 let snappedStart = snapAndQuantize(orig.start + dSec,
2350 excluding: Set(drag.origSelection.keys),
2351 duration: orig.duration)
2352 var delta = snappedStart - orig.start
15432353 let minStart = drag.origSelection.values.map(\.start).min() ?? 0
15442354 if minStart + delta < 0 { delta = -minStart }
15452355
......@@ -1625,6 +2435,16 @@ final class TimelineView: NSView {
16252435 }
16262436 }
16272437
2438 /// A trim/slip drag is exposing source material the clip didn't use
2439 /// before — tell the proxy builder NOW (mid-drag), not at mouse-up, so
2440 /// the newly extended range is often already covered when the user plays
2441 /// it. `direction` is which way the exposure grows in source time.
2442 private func prefetchExposure(_ orig: Clip, sourceTime: Double, direction: Int) {
2443 guard orig.kind == .video, let media = project.media(orig.mediaId) else { return }
2444 ctx.chunks.noteGestureExposure(media: media, sourceTime: sourceTime,
2445 direction: direction)
2446 }
2447
16282448 /// Non-audio clips on the same track whose head would be swallowed by
16292449 /// dragging `orig`'s out-edge to `newEnd` (audio layers freely, so it
16302450 /// never gets pushed).
......@@ -1657,12 +2477,7 @@ final class TimelineView: NSView {
16572477 return
16582478 }
16592479 let media = project.media(orig.mediaId)
1660 var desired = orig.end + dSec
1661 if let adj = snapAdjust(start: desired, duration: 0, excluding: [orig.id]) {
1662 desired += adj.adjust
1663 activeSnapTarget = adj.target
1664 }
1665 desired = quantize(desired)
2480 let desired = snapAndQuantize(orig.end + dSec, excluding: [orig.id])
16662481 var maxEnd = Double.greatestFiniteMagnitude
16672482 if let media {
16682483 maxEnd = orig.start + (media.duration - orig.srcIn) / max(0.001, orig.speed)
......@@ -1677,6 +2492,10 @@ final class TimelineView: NSView {
16772492 // The grabbed clip's clamped change is the delta applied to every
16782493 // selected/linked clip.
16792494 let delta = newEnd - orig.end
2495 if delta > 0 { // extending the tail exposes fresh source material
2496 prefetchExposure(orig, sourceTime: orig.srcIn + (newEnd - orig.start) * orig.speed,
2497 direction: 1)
2498 }
16802499 let targets = drag.origSelection.values.filter { $0.kind != .storyboard }
16812500 let single = targets.count <= 1
16822501 store.updateGesture { model in
......@@ -1703,12 +2522,7 @@ final class TimelineView: NSView {
17032522
17042523 private func dragTrimIn(dSec: Double) {
17052524 guard let orig = drag.origClip else { return }
1706 var desired = orig.start + dSec
1707 if let adj = snapAdjust(start: desired, duration: 0, excluding: [orig.id]) {
1708 desired += adj.adjust
1709 activeSnapTarget = adj.target
1710 }
1711 desired = quantize(desired)
2525 let desired = snapAndQuantize(orig.start + dSec, excluding: [orig.id])
17122526 // Storyboard panels are pure start positions (no source media), so the
17132527 // in-edge just slides the panel's start either way — bounded only by the
17142528 // previous panel (a frame of clearance) and this panel's own end.
......@@ -1732,6 +2546,10 @@ final class TimelineView: NSView {
17322546 minStart = max(0, minStart)
17332547 let maxStart = orig.end - frameDur
17342548 let newStart = min(max(desired, minStart), maxStart)
2549 if newStart < orig.start { // extending the head exposes earlier source
2550 prefetchExposure(orig, sourceTime: orig.srcIn + (newStart - orig.start) * orig.speed,
2551 direction: -1)
2552 }
17352553 let base = store.gestureBaseModel ?? project
17362554 // The grabbed clip's clamped change is the delta applied to every
17372555 // selected/linked clip. A storyboard in-edge just moves that panel's
......@@ -1768,12 +2586,7 @@ final class TimelineView: NSView {
17682586 private func dragRippleOut(dSec: Double) {
17692587 guard let orig = drag.origClip else { return }
17702588 let base = store.gestureBaseModel ?? project
1771 var desired = quantize(orig.end + dSec)
1772 if let adj = snapAdjust(start: desired, duration: 0, excluding: [orig.id]) {
1773 desired += adj.adjust
1774 activeSnapTarget = adj.target
1775 desired = quantize(desired)
1776 }
2589 let desired = snapAndQuantize(orig.end + dSec, excluding: [orig.id])
17772590 var newEnd = max(desired, orig.start + frameDur)
17782591 if orig.kind == .video, let media = base.media(orig.mediaId) {
17792592 newEnd = min(newEnd, orig.start + (media.duration - orig.srcIn) / max(0.001, orig.speed))
......@@ -1785,6 +2598,11 @@ final class TimelineView: NSView {
17852598 // Never push anything below t = 0.
17862599 let minStart = followers.map(\.start).min() ?? 0
17872600 let clampedDelta = max(delta, -minStart)
2601 if clampedDelta > 0 { // ripple-extending the tail exposes fresh source
2602 prefetchExposure(orig,
2603 sourceTime: orig.srcIn + (orig.end + clampedDelta - orig.start) * orig.speed,
2604 direction: 1)
2605 }
17882606 store.updateGesture { model in
17892607 if orig.kind != .storyboard,
17902608 let i = model.clips.firstIndex(where: { $0.id == orig.id }) {
......@@ -1797,10 +2615,54 @@ final class TimelineView: NSView {
17972615 }
17982616 }
17992617
2618 /// ⌥-drag a clip's IN edge: ripple-trim the head while pinning the clip's
2619 /// start. The in-point (`srcIn`) and length change —
2620 /// the clip's OUT edge moves — and every later clip on the track shifts by the
2621 /// same amount so the timeline stays gapless. Unlike a normal trim-in (which
2622 /// slides the clip's start and opens blank space *before* it), the start stays
2623 /// put and the space is taken out of the track. Dragging right trims the head
2624 /// (clip shrinks, followers pull in); left extends it (clip grows, followers
2625 /// push out). The out edge — the source out-point — never moves.
2626 private func dragRippleIn(dSec: Double) {
2627 guard let orig = drag.origClip, orig.kind != .storyboard else { return }
2628 let base = store.gestureBaseModel ?? project
2629 let followers = base.clips.filter {
2630 $0.id != orig.id && $0.track == orig.track && $0.start >= orig.end - 1e-9
2631 }
2632 // Snap the moving OUT edge to static references only — the followers ride
2633 // along with it, so they can't be snap targets. d > 0 trims the head.
2634 var exclude = Set(followers.map(\.id)); exclude.insert(orig.id)
2635 let newEnd = snapAndQuantize(orig.end - dSec, excluding: exclude)
2636 var d = orig.end - newEnd
2637 d = min(d, orig.duration - frameDur) // keep ≥ one frame
2638 d = max(d, -orig.srcIn / max(0.001, orig.speed)) // head ≥ source start
2639 if let firstStart = followers.map(\.start).min() { // followers ≥ t = 0
2640 d = min(d, firstStart)
2641 }
2642 if d < 0 { // ripple-extending the head exposes earlier source
2643 prefetchExposure(orig, sourceTime: orig.srcIn + d * orig.speed, direction: -1)
2644 }
2645 store.updateGesture { model in
2646 guard let i = model.clips.firstIndex(where: { $0.id == orig.id }) else { return }
2647 model.clips[i].srcIn = orig.srcIn + d * orig.speed
2648 model.clips[i].duration = orig.duration - d
2649 for f in followers {
2650 guard let j = model.clips.firstIndex(where: { $0.id == f.id }) else { continue }
2651 model.clips[j].start = f.start - d
2652 }
2653 }
2654 }
2655
18002656 private func dragSlip(dSec: Double) {
18012657 guard let orig = drag.origClip, let media = project.media(orig.mediaId) else { return }
18022658 let maxIn = max(0, media.duration - orig.sourceLength)
18032659 let newIn = min(max(orig.srcIn - dSec * orig.speed, 0), maxIn)
2660 // Slipping reveals source on the side the content is sliding from.
2661 if newIn < orig.srcIn {
2662 prefetchExposure(orig, sourceTime: newIn, direction: -1)
2663 } else if newIn > orig.srcIn {
2664 prefetchExposure(orig, sourceTime: newIn + orig.sourceLength, direction: 1)
2665 }
18042666 store.updateGesture { model in
18052667 guard let i = model.clips.firstIndex(where: { $0.id == drag.clipId }) else { return }
18062668 model.clips[i].srcIn = newIn
......@@ -1847,6 +2709,21 @@ final class TimelineView: NSView {
18472709 }
18482710 }
18492711
2712 /// The single place edge alignment is decided: snap `desired` to nearby clip
2713 /// edges / playhead / 0 (unless snapping is off), record the snap indicator,
2714 /// then frame-quantize — snap FIRST, quantize ONCE. Every drag handler routes
2715 /// through here so trims, moves and ripples align identically (previously each
2716 /// re-implemented this, and the ripple handler quantized twice out of order).
2717 private func snapAndQuantize(_ desired: Double, excluding: Set<UUID>,
2718 duration: Double = 0) -> Double {
2719 var v = desired
2720 if let adj = snapAdjust(start: v, duration: duration, excluding: excluding) {
2721 v += adj.adjust
2722 activeSnapTarget = adj.target
2723 }
2724 return quantize(v)
2725 }
2726
18502727 private func snapAdjust(start: Double, duration: Double, excluding: Set<UUID>)
18512728 -> (adjust: Double, target: Double)? {
18522729 // Holding ⇧ mid-drag temporarily inverts snapping.
......@@ -1882,36 +2759,50 @@ final class TimelineView: NSView {
18822759 }
18832760
18842761 override func mouseMoved(with event: NSEvent) {
1885 let p = convert(event.locationInWindow, from: nil)
1886 lastMousePoint = p
1887 var cursor = NSCursor.arrow
2762 lastMousePoint = convert(event.locationInWindow, from: nil)
2763 cursor(for: event.modifierFlags).set()
2764 }
2765
2766 // Holding a modifier changes what a drag would do, so refresh the cursor even
2767 // when the pointer is still (mouseMoved won't fire on a bare key press).
2768 override func flagsChanged(with event: NSEvent) {
2769 cursor(for: event.modifierFlags).set()
2770 }
2771
2772 /// The cursor for the pointer's current spot, given the held modifiers, so it
2773 /// previews the gesture a drag would start: ⌥ over a clip body slips it, ⌥/⌘
2774 /// or a plain hover over an edge resizes (trim / ripple / stretch).
2775 private func cursor(for mods: NSEvent.ModifierFlags) -> NSCursor {
2776 let p = lastMousePoint
18882777 if let barMode = scrollbarHit(p) {
18892778 switch barMode {
1890 case .hBarLeft, .hBarRight: cursor = .resizeLeftRight
1891 case .vBarTop, .vBarBottom: cursor = .resizeUpDown
1892 default: break
1893 }
1894 } else if trackBoundaryAt(y: p.y) != nil, p.y > lanesTop {
1895 cursor = .resizeUpDown
1896 } else if let (clip, row) = clipAt(point: p) {
1897 if session.mainTool == .blade {
1898 cursor = .crosshair
1899 } else if session.mainTool == .slide {
1900 cursor = .openHand
1901 } else {
1902 let rect = clipRect(clip, row: row)
1903 if p.x - rect.minX < 7 || rect.maxX - p.x < 7 {
1904 cursor = .resizeLeftRight
1905 }
2779 case .hBarLeft, .hBarRight: return .resizeLeftRight
2780 case .vBarTop, .vBarBottom: return .resizeUpDown
2781 default: return .arrow
19062782 }
19072783 }
1908 cursor.set()
2784 if trackBoundaryAt(y: p.y) != nil, p.y > lanesTop { return .resizeUpDown }
2785 guard let (clip, row) = clipAt(point: p) else { return .arrow }
2786 if session.mainTool == .blade { return .crosshair }
2787 if session.mainTool == .slide { return .openHand }
2788 let rect = clipRect(clip, row: row)
2789 let onEdge = p.x - rect.minX < 7 || rect.maxX - p.x < 7
2790 if mods.contains(.option), !onEdge { return .openHand } // ⌥ body: slip
2791 return onEdge ? .resizeLeftRight : .arrow
19092792 }
19102793
19112794 // MARK: - Keyboard (fallbacks; the menu bar owns the canonical bindings)
19122795
19132796 override func keyDown(with event: NSEvent) {
19142797 let pc = playback
2798 // A key pressed WHILE a mouse drag holds an open gesture must not run —
2799 // a mutating shortcut (split/delete/nudge) calls `store.mutate`, whose
2800 // `precondition(gestureBase == nil)` would trap mid-drag. Ignore keys
2801 // until the drag ends; Escape still cancels it.
2802 if store.gestureBaseModel != nil {
2803 if event.keyCode == 53 { cancelOperation(nil) } // Esc → cancel drag
2804 return
2805 }
19152806 switch event.charactersIgnoringModifiers?.lowercased() {
19162807 case " ": pc.togglePlay()
19172808 case "j": pc.shuttle(-1)
......@@ -2745,7 +3636,7 @@ final class TimelineView: NSView {
27453636 add("Rescan Comps", #selector(ctxRescanComps))
27463637 return menu
27473638 }
2748 if fusionBandH > 0, p.y > rulerH, p.y < lanesTop {
3639 if fusionBandH > 0, p.y > fusionTop, p.y < lanesTop {
27493640 add(session.fusionHidden ? "Show Fusion Preview" : "Hide Fusion Preview",
27503641 #selector(ctxToggleFusionHidden))
27513642 add(session.fusionFocus ? "Unfocus Fusion" : "Focus Fusion",
......@@ -2888,7 +3779,8 @@ final class TimelineView: NSView {
28883779 /// past the last clip (the playhead may live out there), but bounded.
28893780 private func clampOrigin(_ o: Double) -> Double {
28903781 let overscroll = 600.0 / pxPerSecond
2891 return min(max(o, -overscroll), project.timelineDuration + 120)
3782 rebuildSceneIfNeeded()
3783 return min(max(o, -overscroll), cachedTimelineDuration + 120)
28923784 }
28933785
28943786 override func scrollWheel(with event: NSEvent) {
......@@ -2905,12 +3797,10 @@ final class TimelineView: NSView {
29053797 originSecond = clampOrigin(originSecond - Double(dx) / pxPerSecond)
29063798 }
29073799 }
2908 noteScrolling()
29093800 needsDisplay = true
29103801 }
29113802
29123803 override func magnify(with event: NSEvent) {
2913 noteScrolling()
29143804 zoom(by: 1 + event.magnification, anchorX: convert(event.locationInWindow, from: nil).x)
29153805 }
29163806
......@@ -2969,7 +3859,7 @@ final class TimelineView: NSView {
29693859 let streams = expandDropStreams(droppableFiles(from: sender))
29703860 guard !streams.isEmpty else { fileDropPreview = nil; needsDisplay = true; return }
29713861 let p = convert(sender.draggingLocation, from: nil)
2972 let count = project.laneRefs.count
3862 let count = laneRows.count
29733863 fileDropPreview = FileDropPreview(
29743864 streams: streams,
29753865 minOffset: streams.map(\.offset).min() ?? 0,
......@@ -3001,7 +3891,7 @@ final class TimelineView: NSView {
30013891 let files = droppableFiles(from: sender)
30023892 guard !files.isEmpty else { return false }
30033893 let dropSec = max(0, quantize(secondsFor(p.x)))
3004 let row = rowAt(y: p.y).flatMap { $0 < project.laneRefs.count ? $0 : nil }
3894 let row = rowAt(y: p.y).flatMap { $0 < laneRows.count ? $0 : nil }
30053895 importFiles(files, atSecond: dropSec, targetRow: row)
30063896 return true
30073897 }
......@@ -3011,7 +3901,7 @@ final class TimelineView: NSView {
30113901 /// track exactly the way dragging an existing clip down does.
30123902 private func drawFileDropPreview() {
30133903 guard let dp = fileDropPreview else { return }
3014 let count = project.laneRefs.count
3904 let count = laneRows.count
30153905 for (i, s) in dp.streams.enumerated() {
30163906 let row = dp.baseRow + i
30173907 let lane = laneRect(row: row)
......@@ -3167,7 +4057,6 @@ final class TimelineView: NSView {
31674057 func testVThumb() -> NSRect { vThumbRect() }
31684058 func testSetOrigin(_ sec: Double) { originSecond = sec }
31694059 func testSetPxPerSecond(_ p: Double) { pxPerSecond = p }
3170 func testSetScrolling(_ b: Bool) { isScrolling = b }
31714060 /// Draw straight into the current graphics context (set by the harness),
31724061 /// bypassing `cacheDisplay`'s per-call bitmap allocation so we time `draw`.
31734062 func testRedraw() { draw(bounds) }
sequencer/Sources/Sequencer/Tools.swift+46-6
......@@ -220,6 +220,8 @@ final class SettingsWindow: NSObject {
220220 private let compsLabel = NSTextField(labelWithString: "—")
221221 // Global tab
222222 private let cacheField = NSTextField(string: "")
223 private let ramField = NSTextField(string: "")
224 private let usageLabel = NSTextField(labelWithString: "")
223225 private let cacheLabel = NSTextField(labelWithString: "")
224226
225227 // Aspect label → concrete storyboard resolution (stored in the model as
......@@ -231,9 +233,9 @@ final class SettingsWindow: NSObject {
231233
232234 func show() {
233235 buildIfNeeded()
234 sync()
235 window?.makeKeyAndOrderFront(nil)
236 window?.makeKeyAndOrderFront(nil) // visible first — sync() guards on it
236237 NSApp.activate(ignoringOtherApps: true)
238 sync()
237239 }
238240
239241 private func label(_ s: String) -> NSTextField {
......@@ -242,9 +244,17 @@ final class SettingsWindow: NSObject {
242244 return l
243245 }
244246
247 /// Key (fields need focus) but never MAIN: `DocumentContext.current`
248 /// resolves through the main window, so a main Settings window would read
249 /// a blank headless project (fps/comps/estimate all defaults) AND starve
250 /// the front-document build gate while it's open.
251 private final class SettingsPanel: NSWindow {
252 override var canBecomeMain: Bool { false }
253 }
254
245255 private func buildIfNeeded() {
246256 guard window == nil else { return }
247 let w = NSWindow(
257 let w = SettingsPanel(
248258 contentRect: NSRect(x: 0, y: 0, width: 560, height: 300),
249259 styleMask: [.titled, .closable],
250260 backing: .buffered, defer: false)
......@@ -300,13 +310,27 @@ final class SettingsWindow: NSObject {
300310 cacheField.widthAnchor.constraint(equalToConstant: 60).isActive = true
301311 let cacheRow = NSStackView(views: [cacheField, NSTextField(labelWithString: "GB")])
302312 cacheRow.spacing = 4
313 ramField.target = self
314 ramField.action = #selector(ramChanged)
315 ramField.widthAnchor.constraint(equalToConstant: 60).isActive = true
316 let ramRow = NSStackView(views: [ramField, NSTextField(labelWithString: "GB")])
317 ramRow.spacing = 4
318 let ramNote = NSTextField(labelWithString:
319 "Decoded stand-in frames + player read-ahead; more = fewer hiccups.")
320 ramNote.textColor = .secondaryLabelColor
321 ramNote.font = .systemFont(ofSize: 11)
303322 let reveal = NSButton(title: "Reveal Cache", target: self, action: #selector(revealCache))
304323 reveal.controlSize = .small
324 usageLabel.textColor = .secondaryLabelColor
325 usageLabel.font = .systemFont(ofSize: 11)
305326 cacheLabel.textColor = .secondaryLabelColor
306327 cacheLabel.font = .systemFont(ofSize: 11)
307328
308329 let globalGrid = NSGridView(views: [
309330 [label("Proxy cache limit"), cacheRow],
331 [label("RAM frame cache"), ramRow],
332 [NSView(), ramNote],
333 [NSView(), usageLabel],
310334 [NSView(), reveal],
311335 [NSView(), cacheLabel],
312336 ])
......@@ -343,7 +367,9 @@ final class SettingsWindow: NSObject {
343367 }
344368
345369 @objc private func sync() {
346 guard window != nil else { return }
370 // The estimate below walks each media's chunk dir — fine on demand,
371 // wasteful on every .projectChanged while the window is closed.
372 guard let window, window.isVisible else { return }
347373 let project = DocumentContext.current.store.project
348374 // Rebuild the fps popup: presets plus the project's own rate when it's
349375 // not a preset (e.g. 29.50 fps probed from a screen recording).
......@@ -363,7 +389,14 @@ final class SettingsWindow: NSObject {
363389 compsLabel.stringValue = project.compsFolder ?? "not set"
364390 let gb = UserDefaults.standard.integer(forKey: "maxCacheGB")
365391 cacheField.stringValue = "\(gb > 0 ? gb : 50)"
366 cacheLabel.stringValue = "Cache: \(MediaPipeline.shared.cacheRoot.path)"
392 ramField.stringValue = "\(FrameCache.ramGB)"
393 let pipeline = MediaPipeline.shared
394 let est = DocumentContext.current.chunks.optimizeEstimate(for: project)
395 usageLabel.stringValue = String(
396 format: "Using %.1f of %.0f GB · this project fully optimized ≈ %.1f GB (%.1f GB built)",
397 Double(pipeline.ledgerBytes) / 1e9, Double(pipeline.maxCacheBytes) / 1e9,
398 Double(est.total) / 1e9, Double(est.built) / 1e9)
399 cacheLabel.stringValue = "Cache: \(pipeline.cacheRoot.path)"
367400 }
368401
369402 @objc private func fpsChanged() {
......@@ -393,7 +426,14 @@ final class SettingsWindow: NSObject {
393426 @objc private func cacheChanged() {
394427 let gb = Int(cacheField.stringValue) ?? 50
395428 UserDefaults.standard.set(max(1, gb), forKey: "maxCacheGB")
396 MediaPipeline.shared.evictIfNeeded()
429 MediaPipeline.shared.evictIfNeeded(reconcile: true)
430 sync()
431 }
432 @objc private func ramChanged() {
433 let gb = Int(ramField.stringValue) ?? 2
434 UserDefaults.standard.set(max(1, gb), forKey: "maxRAMGB")
435 FrameCache.shared.refreshBudget()
436 sync()
397437 }
398438 @objc private func revealCache() {
399439 NSWorkspace.shared.activateFileViewerSelecting([MediaPipeline.shared.cacheRoot])
sequencer/Sources/Sequencer/Transcript.swift created+469
......@@ -0,0 +1,469 @@
1import AppKit
2
3// Clover-recorder transcript integration: when a `cam.mov` has a sibling
4// `transcript.json` (the format the recorder writes), the viewer shows
5// synchronized two-line karaoke captions over the picture.
6//
7// All transcript times are in the media's OWN (source) timebase — seconds from
8// the start of cam.mov — so a clip's `sourceTime(at:)` maps the playhead into
9// them, respecting trim (srcIn) and speed.
10
11/// One spoken word with its timing (source seconds) and which segment
12/// (sentence) it came from — segment changes force a line break so sentences
13/// don't run together.
14struct TranscriptWord {
15 var text: String
16 var start: Double
17 var end: Double
18 var seg: Int
19}
20
21/// A parsed clover `transcript.json`: words flattened across segments in time
22/// order. `key` is the file path — its identity for layout caching.
23struct Transcript {
24 var words: [TranscriptWord]
25 var key: String
26
27 // MARK: Decoding shapes (tolerant — unknown/missing fields are skipped)
28
29 private struct RawWord: Decodable { var word: String?; var start: Double?; var end: Double? }
30 private struct RawSeg: Decodable {
31 var start: Double?; var end: Double?; var text: String?; var words: [RawWord]?
32 }
33 private struct RawTranscript: Decodable { var segments: [RawSeg]? }
34
35 /// Parse raw JSON. Returns nil if there's nothing usable to show.
36 static func parse(_ data: Data, key: String) -> Transcript? {
37 guard let raw = try? JSONDecoder().decode(RawTranscript.self, from: data),
38 let segments = raw.segments else { return nil }
39 var words: [TranscriptWord] = []
40 for (si, seg) in segments.enumerated() {
41 let wordList = seg.words ?? []
42 var added = false
43 for rw in wordList {
44 guard let start = rw.start else { continue }
45 let text = (rw.word ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
46 guard !text.isEmpty else { continue }
47 words.append(TranscriptWord(text: text, start: start,
48 end: max(start, rw.end ?? start), seg: si))
49 added = true
50 }
51 // A segment with no per-word timing still shows as one block spanning
52 // the segment, so nothing goes silently missing.
53 if !added, let start = seg.start {
54 let text = (seg.text ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
55 if !text.isEmpty {
56 words.append(TranscriptWord(text: text, start: start,
57 end: max(start, seg.end ?? start), seg: si))
58 }
59 }
60 }
61 guard !words.isEmpty else { return nil }
62 // Recorder output is monotonic in start time; sort defensively (stable,
63 // so an already-sorted file keeps its segment contiguity) so the
64 // active-word binary search below is always valid.
65 words.sort { $0.start < $1.start }
66 return Transcript(words: words, key: key)
67 }
68
69 // MARK: Sibling-file loading (cached)
70
71 private static var cache: [String: Transcript?] = [:]
72
73 /// The transcript for a media file, or nil unless it is named `cam.mov` and
74 /// has a readable `transcript.json` in the same folder. Cached by path so
75 /// the per-frame visibility probe is cheap.
76 static func load(forVideo url: URL) -> Transcript? {
77 guard url.lastPathComponent.lowercased() == "cam.mov" else { return nil }
78 let path = url.deletingLastPathComponent()
79 .appendingPathComponent("transcript.json").path
80 if let hit = cache[path] { return hit }
81 let parsed = (try? Data(contentsOf: URL(fileURLWithPath: path)))
82 .flatMap { parse($0, key: path) }
83 cache[path] = parsed
84 return parsed
85 }
86
87 /// Drop cached parses so a re-imported/edited transcript is re-read.
88 static func clearCache() { cache.removeAll() }
89
90 // MARK: Lookup
91
92 /// Index of the word active at source time `s` — the last word that has
93 /// started. A word stays "current" until the next one begins, so there are
94 /// no gaps. Returns -1 before the first word.
95 func activeIndex(at s: Double) -> Int {
96 var lo = 0, hi = words.count - 1, res = -1
97 while lo <= hi {
98 let mid = (lo + hi) / 2
99 if words[mid].start <= s { res = mid; lo = mid + 1 } else { hi = mid - 1 }
100 }
101 return res
102 }
103}
104
105/// Two-line synchronized captions drawn over the viewer: white text on a black
106/// clipped background, the active word in magenta, always on the TOP line.
107/// New lines rise from the bottom and leave above the top as speech advances.
108///
109/// **The whole thing is a pure function of the playhead.** Nothing here uses a
110/// timer, Core Animation, or wall-clock tween: the vertical scroll offset is
111/// computed straight from the source time, so stepping frame-by-frame steps the
112/// animation and seeking lands with no motion (exactly what was asked for).
113final class SubtitleOverlay: NSView {
114 var ctx: DocumentContext = .headless {
115 didSet {
116 guard oldValue !== ctx else { return }
117 oldValue.notify.removeObserver(self, name: .playheadChanged, object: nil)
118 ctx.notify.addObserver(self, selector: #selector(sync),
119 name: .playheadChanged, object: nil)
120 sync()
121 }
122 }
123 private var store: Store { ctx.store }
124 private var playback: PlaybackController { ctx.playback }
125 private var session: SessionState { ctx.session }
126
127 /// The transcript being shown and the source time to render it at — both
128 /// recomputed on every playhead move in `sync()`.
129 private var transcript: Transcript?
130 private var sourceTime: Double = 0
131 /// The clip the transcript is being shown for — kept so a word click can map
132 /// the word's source time back to a timeline moment (inverse of the scroll).
133 private var clip: Clip?
134
135 /// Laid-out lines (each an array of positioned words) cached per transcript
136 /// + box width. `lineOfWord[i]` is the line word `i` landed on.
137 private struct LaidWord { var index: Int; var text: String; var x: CGFloat; var width: CGFloat }
138 private var lines: [[LaidWord]] = []
139 private var lineOfWord: [Int] = []
140 /// Natural content width (points) of each laid-out line — drives the black
141 /// backing that hugs the visible two lines.
142 private var lineWidths: [CGFloat] = []
143 private var laidKey: String?
144 private var laidWidth: CGFloat = -1
145
146 /// What to mark at the current source time: a spoken word painted magenta,
147 /// or — during a real pause between two words — a magenta caret sitting
148 /// after the last spoken word, signalling that nothing is being said.
149 private enum Mark { case word(Int); case caret(after: Int); case none }
150
151 /// Gaps up to this (seconds) hold the previous word lit instead of blinking
152 /// off; longer gaps show the caret. Small inter-word silences are noise and
153 /// shouldn't flicker the highlight.
154 private static let extendGap = 0.25
155
156 private static let magenta = NSColor(srgbRed: 1.0, green: 0.22, blue: 0.86, alpha: 1)
157
158 override init(frame: NSRect) {
159 super.init(frame: frame)
160 wantsLayer = true
161 layer?.backgroundColor = NSColor.clear.cgColor
162 isHidden = true
163 NotificationCenter.default.addObserver(self, selector: #selector(sync),
164 name: .projectChanged, object: nil)
165 NotificationCenter.default.addObserver(self, selector: #selector(sync),
166 name: .viewOptionsChanged, object: nil)
167 NotificationCenter.default.addObserver(self, selector: #selector(sync),
168 name: .viewerNeedsRefresh, object: nil)
169 ctx.notify.addObserver(self, selector: #selector(sync),
170 name: .playheadChanged, object: nil)
171 }
172 required init?(coder: NSCoder) { fatalError() }
173
174 override var isFlipped: Bool { true } // y grows downward: top line at small y
175
176 // Swallow clicks only when they land on a word — a word click scrubs the
177 // playhead to that word (see `mouseDown`). Clicks on the box's gaps/padding
178 // pass through to the cell beneath so clip selection still works there.
179 override func hitTest(_ point: NSPoint) -> NSView? {
180 guard !isHidden, transcript != nil else { return nil }
181 return wordIndex(atLocal: convert(point, from: superview)) != nil ? self : nil
182 }
183
184 /// Scrub to the clicked word's start.
185 override func mouseDown(with event: NSEvent) {
186 let local = convert(event.locationInWindow, from: nil)
187 guard let transcript, let clip,
188 let idx = wordIndex(atLocal: local) else { return }
189 playback.seek(to: clip.timelineTime(forSource: transcript.words[idx].start))
190 }
191
192 /// The transcript word whose drawn rect contains `p` (this view's flipped,
193 /// local coords), or nil. Mirrors `draw`'s layout so hit-testing and painting
194 /// never disagree.
195 private func wordIndex(atLocal p: NSPoint) -> Int? {
196 guard transcript != nil, bounds.width > 40 else { return nil }
197 rebuildLayoutIfNeeded()
198 guard !lines.isEmpty else { return nil }
199 let m = Metrics(width: bounds.width)
200 // Only the visible two-line band is clickable (matches what's drawn).
201 guard p.y >= m.vpad, p.y <= bounds.height - m.vpad else { return nil }
202 let scroll = scrollLines(at: sourceTime)
203 for (li, line) in lines.enumerated() {
204 let topY = m.vpad + (CGFloat(li) - scroll) * m.lineH
205 guard p.y >= topY, p.y < topY + m.lineH else { continue }
206 for lw in line where p.x >= lw.x && p.x < lw.x + lw.width {
207 return lw.index
208 }
209 }
210 return nil
211 }
212
213 // MARK: Geometry
214
215 /// Box metrics for a given width. Font scales gently with the box so the
216 /// captions stay legible on small viewers without ballooning on large ones.
217 struct Metrics {
218 let fontSize, lineH, vpad, hpad, height: CGFloat
219 init(width: CGFloat) {
220 fontSize = min(26, max(14, width / 30))
221 lineH = (fontSize * 1.32).rounded(.up)
222 vpad = (lineH * 0.34).rounded()
223 hpad = 16
224 height = lineH * 2 + vpad * 2
225 }
226 }
227
228 private static func font(_ size: CGFloat) -> NSFont {
229 .systemFont(ofSize: size, weight: .semibold)
230 }
231
232 /// Where the caption box sits inside the viewer: centred horizontally, near
233 /// the bottom. Sized only from the viewer bounds, so the parent can position
234 /// it in `layout()` without knowing the content.
235 func preferredFrame(in bounds: NSRect) -> NSRect {
236 let w = min(max(bounds.width * 0.72, 260), 860)
237 let m = Metrics(width: w)
238 let x = ((bounds.width - w) / 2).rounded()
239 let margin = max(16, bounds.height * 0.045)
240 let y = max(m.vpad, (bounds.height - m.height - margin).rounded())
241 return NSRect(x: x, y: y, width: w, height: m.height)
242 }
243
244 // MARK: State
245
246 /// Recompute which transcript/clip is under the playhead and at what source
247 /// time, then redraw. Hidden whenever the toggle is off or nothing under the
248 /// playhead carries a transcript.
249 @objc func sync() {
250 guard session.subtitlesEnabled, let (clip, media) = transcriptClipUnderPlayhead() else {
251 if !isHidden { isHidden = true }
252 transcript = nil
253 self.clip = nil
254 return
255 }
256 transcript = Transcript.load(forVideo: media.url)
257 self.clip = clip
258 sourceTime = max(0, clip.sourceTime(at: playback.playhead))
259 isHidden = transcript == nil
260 needsDisplay = true
261 }
262
263 /// The visible video clip under the playhead whose media is a transcript-
264 /// bearing `cam.mov`. Mirrors the viewer's own hide/focus visibility rule;
265 /// the Priority pane wins when it qualifies.
266 private func transcriptClipUnderPlayhead() -> (Clip, MediaItem)? {
267 let project = store.project
268 let t = playback.playhead
269 let focusActive = !session.focusedTracks.isEmpty || session.fusionFocus
270 func visible(_ ref: TrackRef) -> Bool {
271 focusActive ? session.focusedTracks.contains(ref)
272 : !session.hiddenTracks.contains(ref)
273 }
274 var found: (TrackRef, Clip, MediaItem)?
275 for ref in project.laneRefs where visible(ref) {
276 guard let clip = project.clipAt(track: ref, time: t, kind: .video),
277 let m = project.media(clip.mediaId),
278 Transcript.load(forVideo: m.url) != nil else { continue }
279 if ref == session.priorityPane { return (clip, m) }
280 if found == nil { found = (ref, clip, m) }
281 }
282 return found.map { ($0.1, $0.2) }
283 }
284
285 // MARK: Layout
286
287 /// Wrap the transcript into center-aligned lines that fit the box, breaking
288 /// at segment (sentence) boundaries. Cached until the box width or the
289 /// transcript changes.
290 private func rebuildLayoutIfNeeded() {
291 guard let transcript else { lines = []; lineOfWord = []; lineWidths = []; return }
292 let m = Metrics(width: bounds.width)
293 if laidKey == transcript.key && abs(laidWidth - bounds.width) < 0.5 { return }
294 laidKey = transcript.key
295 laidWidth = bounds.width
296
297 let font = Self.font(m.fontSize)
298 let attrs: [NSAttributedString.Key: Any] = [.font: font]
299 func measure(_ s: String) -> CGFloat { (s as NSString).size(withAttributes: attrs).width }
300 let space = measure(" ")
301 let maxW = bounds.width - m.hpad * 2
302
303 var built: [[LaidWord]] = []
304 var widths: [CGFloat] = []
305 var lineIdx = [Int](repeating: 0, count: transcript.words.count)
306 var cur: [LaidWord] = []
307 var penX: CGFloat = 0
308
309 func flush() {
310 guard !cur.isEmpty else { return }
311 let lineW = (cur.last?.x ?? 0) + (cur.last?.width ?? 0)
312 let off = (m.hpad + max(0, (maxW - lineW) / 2)).rounded()
313 built.append(cur.map { LaidWord(index: $0.index, text: $0.text,
314 x: $0.x + off, width: $0.width) })
315 widths.append(lineW)
316 cur = []
317 penX = 0
318 }
319
320 for (gi, w) in transcript.words.enumerated() {
321 let width = measure(w.text)
322 let newSegment = gi > 0 && w.seg != transcript.words[gi - 1].seg
323 let x = cur.isEmpty ? 0 : penX + space
324 if newSegment || (!cur.isEmpty && x + width > maxW) {
325 flush()
326 }
327 let placeX = cur.isEmpty ? 0 : penX + space
328 cur.append(LaidWord(index: gi, text: w.text, x: placeX, width: width))
329 penX = placeX + width
330 lineIdx[gi] = built.count // the line this word will land on once flushed
331 }
332 flush()
333 lines = built
334 lineOfWord = lineIdx
335 lineWidths = widths
336 }
337
338 /// Content width (points) to draw the black backing at for a given vertical
339 /// scroll. At rest on line `k` this is the wider of the two visible lines
340 /// (`k` and `k+1`); mid-slide it blends the outgoing and incoming pairs, so
341 /// the box width eases in lockstep with the scroll — same playhead-driven,
342 /// frame-steppable motion, no Core Animation.
343 private func contentWidth(atScroll s: CGFloat) -> CGFloat {
344 guard !lineWidths.isEmpty else { return 0 }
345 func pair(_ k: Int) -> CGFloat {
346 let a = (k >= 0 && k < lineWidths.count) ? lineWidths[k] : 0
347 let b = (k + 1 >= 0 && k + 1 < lineWidths.count) ? lineWidths[k + 1] : 0
348 return max(a, b)
349 }
350 let k = Int(s.rounded(.down))
351 let f = s - CGFloat(k)
352 return pair(k) + (pair(k + 1) - pair(k)) * f
353 }
354
355 /// Continuous vertical scroll, in line units, as a pure function of source
356 /// time. Holds on the active word's line, then slides up over ~0.3s when a
357 /// new line begins — so the focused word slides into the top row and stays
358 /// there. Interpolating over time (not a CA animation) is what makes it
359 /// frame-steppable and animation-free on a seek.
360 private func scrollLines(at s: Double) -> CGFloat {
361 guard let transcript, !lineOfWord.isEmpty else { return 0 }
362 let i = transcript.activeIndex(at: s)
363 guard i >= 0 else { return 0 }
364 let cur = lineOfWord[i]
365 let prev = i > 0 ? lineOfWord[i - 1] : cur
366 if cur == prev { return CGFloat(cur) }
367 let startI = transcript.words[i].start
368 let nextStart = i + 1 < transcript.words.count
369 ? transcript.words[i + 1].start : transcript.words[i].end
370 let slide = min(0.30, max(0.0001, nextStart - startI))
371 let t = min(1, max(0, (s - startI) / slide))
372 let e = t * t * (3 - 2 * t) // smoothstep
373 return CGFloat(prev) + (CGFloat(cur) - CGFloat(prev)) * e
374 }
375
376 /// Resolve what to highlight at source time `s`. While a word is being
377 /// spoken it lights up. In the gap after it: a *small* gap holds the word lit
378 /// right up to the next one (so brief silences don't flicker); a *longer* gap
379 /// shows a caret between the two words, marking the pause. Trailing silence
380 /// after the final word fades to nothing.
381 private func mark(at s: Double) -> Mark {
382 guard let transcript else { return .none }
383 let i = transcript.activeIndex(at: s)
384 guard i >= 0 else { return .none }
385 let w = transcript.words[i]
386 if s <= w.end { return .word(i) } // still being spoken
387 if i + 1 < transcript.words.count {
388 let gap = transcript.words[i + 1].start - w.end
389 return gap <= Self.extendGap ? .word(i) // tiny gap: extend it
390 : .caret(after: i) // real pause: caret
391 }
392 return s <= w.end + 0.05 ? .word(i) : .none // trailing silence
393 }
394
395 // MARK: Draw
396
397 override func draw(_ dirty: NSRect) {
398 guard transcript != nil, bounds.width > 40 else { return }
399 rebuildLayoutIfNeeded()
400 guard !lines.isEmpty else { return }
401 let m = Metrics(width: bounds.width)
402
403 let font = Self.font(m.fontSize)
404 let scroll = scrollLines(at: sourceTime)
405 let markResult = mark(at: sourceTime)
406 var highlight = -1
407 if case .word(let idx) = markResult { highlight = idx }
408
409 // Black rounded backing, sized to hug the two visible lines and centred
410 // in the (fixed, transparent) container — width eases with the scroll.
411 let boxW = min(bounds.width, (contentWidth(atScroll: scroll) + m.hpad * 2).rounded())
412 let boxX = ((bounds.width - boxW) / 2).rounded()
413 let boxRect = NSRect(x: boxX, y: 0, width: boxW, height: bounds.height)
414 let bg = NSBezierPath(roundedRect: boxRect, xRadius: 10, yRadius: 10)
415 NSColor(calibratedWhite: 0, alpha: 0.8).setFill()
416 bg.fill()
417 NSGraphicsContext.saveGraphicsState()
418 bg.addClip()
419 // Clip text to the inner TWO-line band (inset by the vertical padding),
420 // not the full padded box. This is what keeps the focused word pinned to
421 // the top line: without it, a settled neighbour line's descenders bleed
422 // into the top/bottom padding and the active line reads as the 2nd row.
423 // Lines still animate through this band — they're simply cut off cleanly
424 // at its top/bottom edges as they rise away / come up from below. The
425 // rounded-box clip above also trims lines sliding through a narrower box.
426 NSBezierPath(rect: NSRect(x: 0, y: m.vpad,
427 width: bounds.width,
428 height: bounds.height - m.vpad * 2)).addClip()
429
430 // Vertical inset so the glyphs sit centred in their line box.
431 let glyphH = font.ascender - font.descender
432 let textInset = ((m.lineH - glyphH) / 2).rounded()
433
434 // A caret (magenta cursor) sits just after the last spoken word during a
435 // real pause. Resolve its line/x from that word's laid-out position.
436 var caretLine = -1
437 var caretX: CGFloat = 0
438 if case .caret(let after) = markResult, after < lineOfWord.count {
439 let li = lineOfWord[after]
440 if li < lines.count, let prev = lines[li].first(where: { $0.index == after }) {
441 caretLine = li
442 let rightEdge = prev.x + prev.width
443 // Centre the caret in the gap to the next word when it shares
444 // this line; if the next word wrapped away, sit just past this one.
445 if let next = lines[li].first(where: { $0.index == after + 1 }) {
446 caretX = ((rightEdge + next.x) / 2).rounded()
447 } else {
448 caretX = (rightEdge + 3).rounded()
449 }
450 }
451 }
452
453 for (li, line) in lines.enumerated() {
454 let topY = m.vpad + (CGFloat(li) - scroll) * m.lineH
455 if topY > bounds.height || topY + m.lineH < 0 { continue } // fully clipped
456 let baselineY = topY + textInset
457 for lw in line {
458 let color = lw.index == highlight ? Self.magenta : NSColor.white
459 let attrs: [NSAttributedString.Key: Any] = [.font: font, .foregroundColor: color]
460 (lw.text as NSString).draw(at: NSPoint(x: lw.x, y: baselineY), withAttributes: attrs)
461 }
462 if li == caretLine {
463 Self.magenta.setFill()
464 NSBezierPath(rect: NSRect(x: caretX - 1, y: baselineY, width: 2, height: glyphH)).fill()
465 }
466 }
467 NSGraphicsContext.restoreGraphicsState()
468 }
469}
sequencer/Sources/Sequencer/TransportBar.swift+25-2
......@@ -90,6 +90,7 @@ final class TransportBar: NSView {
9090 private let shapesButton = InstantButton(title: "", target: nil, action: nil)
9191 private let colorSwatch = InstantButton(title: "", target: nil, action: nil)
9292 private let snapButton = InstantButton(title: "", target: nil, action: nil)
93 private let subtitleButton = InstantButton(title: "", target: nil, action: nil)
9394 private let filmstripButton = InstantButton(title: "", target: nil, action: nil)
9495 private let viewerButton = InstantButton(title: "", target: nil, action: nil)
9596
......@@ -237,6 +238,11 @@ final class TransportBar: NSView {
237238 snapButton.target = self
238239 snapButton.action = #selector(toggleSnap)
239240 styleIconButton(snapButton, tip: "Snap (Y)")
241 subtitleButton.image = NSImage(systemSymbolName: "captions.bubble",
242 accessibilityDescription: "subtitles")
243 subtitleButton.target = self
244 subtitleButton.action = #selector(toggleSubtitles)
245 styleIconButton(subtitleButton, tip: "Subtitles")
240246 filmstripButton.image = NSImage(systemSymbolName: "film",
241247 accessibilityDescription: "clip thumbnails")
242248 filmstripButton.target = self
......@@ -257,8 +263,8 @@ final class TransportBar: NSView {
257263 heightSlider.toolTip = "Track height (⌥⌘= / ⌥⌘- / ⌥⌘0)"
258264 heightSlider.widthAnchor.constraint(equalToConstant: 80).isActive = true
259265
260 let rightStack = NSStackView(views: [netWarn, jobs, snapButton, filmstripButton,
261 viewerButton, heightSlider])
266 let rightStack = NSStackView(views: [netWarn, jobs, snapButton, subtitleButton,
267 filmstripButton, viewerButton, heightSlider])
262268 rightStack.orientation = .horizontal
263269 rightStack.spacing = 4
264270 rightStack.setCustomSpacing(8, after: netWarn)
......@@ -390,6 +396,7 @@ final class TransportBar: NSView {
390396 }
391397
392398 @objc private func toggleSnap() { session.snapping.toggle() }
399 @objc private func toggleSubtitles() { session.subtitlesEnabled.toggle() }
393400 @objc private func toggleFilmstrips() { session.showFilmstrips.toggle() }
394401
395402 @objc private func viewerClicked() {
......@@ -614,6 +621,7 @@ final class TransportBar: NSView {
614621 shapesButton.alphaValue = canDraw ? 1 : 0.3
615622 colorSwatch.layer?.backgroundColor = session.drawColor.cgColor
616623 highlight(snapButton, session.snapping)
624 highlight(subtitleButton, session.subtitlesEnabled)
617625 highlight(filmstripButton, session.showFilmstrips)
618626 highlight(viewerButton, session.previewsOnLeft
619627 || ((window?.windowController as? SequencerWindowController)?.previewsArePopped ?? false))
......@@ -667,6 +675,21 @@ final class TransportBar: NSView {
667675 jobs.textColor = Theme.subtleLabel
668676 jobs.stringValue = "⏸ \(total) chunk\(total == 1 ? "" : "s")"
669677 jobs.toolTip = "When paused, clip optimization happens only during playback."
678 } else if chunks.budgetStarved {
679 // The cache is at its cap and the project is bigger than it: the
680 // builder is deliberately NOT trying to finish the queue — it
681 // maintains a working set around where you play and edit. Say so,
682 // instead of dangling a queue count that will never drain.
683 jobs.textColor = Theme.subtleLabel
684 jobs.stringValue = building > 0
685 ? "cache full — optimizing \(building) near playhead"
686 : "cache full — optimizing on demand"
687 let gb = Double(MediaPipeline.shared.maxCacheBytes) / 1e9
688 jobs.toolTip = String(format:
689 "The proxy cache is at its %.0f GB cap, so this project can't be "
690 + "fully optimized at once. Proxies are kept where you play, edit, "
691 + "and land on clips, and rebuilt on demand elsewhere. Raise the "
692 + "cap in Settings to fit more. Click to pause optimization.", gb)
670693 } else if queued == 0 {
671694 jobs.textColor = .systemOrange
672695 jobs.stringValue = "optimizing \(building) chunk\(building == 1 ? "" : "s")"
sequencer/Sources/Sequencer/UITest.swift+46-1
......@@ -37,7 +37,8 @@ func runUITest() {
3737
3838 // Coordinate helpers mirroring the view's layout constants.
3939 func x(_ sec: Double) -> CGFloat { timeline.testXFor(sec) }
40 func laneY(_ row: Int) -> CGFloat { 26 + CGFloat(row) * (64 + 4) + 4 + 32 } // lane mid
40 // lane mid: ruler (26) + optimization strip (5) + row offset
41 func laneY(_ row: Int) -> CGFloat { 26 + 5 + CGFloat(row) * (64 + 4) + 4 + 32 }
4142 // NSEvent locationInWindow is bottom-left origin; view is flipped & fills window.
4243 func winPoint(_ vx: CGFloat, _ vy: CGFloat) -> NSPoint { NSPoint(x: vx, y: 400 - vy) }
4344
......@@ -487,6 +488,16 @@ func runUITest() {
487488 let top = lum(comp27, 0.5, 0.15), bottom = lum(comp27, 0.5, 0.85)
488489 check(top >= 0 && top < 0.5 && bottom > 0.9,
489490 "stroke drawn at top STAYS at top (top=\(top), bottom=\(bottom))")
491
492 // 27e. The stroke rides the SHARED undo timeline: ⌘Z clears the drawing,
493 // ⌘⇧Z brings it back — no separate raster-undo lane.
494 store.undo()
495 let undone = lum(DocumentContext.headless.boards.composite(for: board27), 0.5, 0.15)
496 store.redo()
497 let redone = lum(DocumentContext.headless.boards.composite(for: board27), 0.5, 0.15)
498 check(undone > 0.9 && redone < 0.5,
499 "raster stroke undoes and redoes on the shared undo timeline "
500 + "(drawn=\(top) undone=\(undone) redone=\(redone))")
490501 DocumentContext.headless.boards.saveRaster(nil, boardId: board27.id)
491502
492503 // 28. Overlaps ignore audio (layering is allowed).
......@@ -597,6 +608,40 @@ func runUITest() {
597608 check(lcs.count == 2 && abs(lcs[1].start - 20) < 1e-6 && abs(lcs[1].duration - 15) < 1e-6,
598609 "⌥← ripple-trims the left side to the playhead")
599610
611 // 31d-ripple. ⌥-drag a clip's IN edge = ripple trim: the clip's start stays
612 // pinned, its in-point re-trims, and the follower shifts to stay gapless.
613 // Media out-point is fixed, so the source range only loses (or regains)
614 // frames at the head.
615 var mri = ProjectModel(); mri.fps = 30; mri.media = [media]
616 mri.tracks = [Track(hue: 0.6)]
617 let ra = Clip(mediaId: media.id, track: .video(0), start: 20, srcIn: 10, duration: 20)
618 let rb = Clip(mediaId: media.id, track: .video(0), start: 40, srcIn: 0, duration: 20)
619 mri.clips = [ra, rb]
620 store.replaceForTest(mri)
621 store.selection = []
622 DocumentContext.headless.session.snapping = false
623 timeline.zoomToFit()
624 // Drag ra's in edge right by 10s: trim the head, pull rb in with it.
625 drag(from: winPoint(x(20) + 3, laneY(0)), to: winPoint(x(30) + 3, laneY(0)), flags: [.option])
626 let ra1 = clip(ra.id)!, rb1 = clip(rb.id)!
627 check(abs(ra1.start - 20) < 1e-6 && abs(ra1.srcIn - 20) < 0.5
628 && abs(ra1.duration - 10) < 0.5 && abs(rb1.start - 30) < 0.5,
629 "⌥ in-edge ripple: start pinned, head trimmed, follower pulled in "
630 + "(got start \(ra1.start), srcIn \(ra1.srcIn), dur \(ra1.duration), rb \(rb1.start))")
631 check(abs((ra1.srcIn + ra1.duration) - (ra.srcIn + ra.duration)) < 0.5,
632 "⌥ in-edge ripple keeps the source out-point fixed")
633 store.undo()
634 check(abs(clip(ra.id)!.srcIn - 10) < 1e-6 && abs(clip(rb.id)!.start - 40) < 1e-6,
635 "⌥ in-edge ripple undoes as one step")
636 // Drag left by 10s: extend the head, push the follower out.
637 drag(from: winPoint(x(20) + 3, laneY(0)), to: winPoint(x(10) + 3, laneY(0)), flags: [.option])
638 let ra2 = clip(ra.id)!, rb2 = clip(rb.id)!
639 check(abs(ra2.start - 20) < 1e-6 && abs(ra2.srcIn - 0) < 0.5
640 && abs(ra2.duration - 30) < 0.5 && abs(rb2.start - 50) < 0.5,
641 "⌥ in-edge ripple (drag left): head extended, follower pushed out "
642 + "(got start \(ra2.start), srcIn \(ra2.srcIn), dur \(ra2.duration), rb \(rb2.start))")
643 store.undo()
644
600645 // 31e. Delete-the-space closes a blank gap at the playhead.
601646 var mb = ProjectModel(); mb.fps = 30; mb.media = [media]
602647 mb.tracks = [Track(hue: 0.4)]
sequencer/Sources/Sequencer/ViewerGridView.swift+121-15
......@@ -20,6 +20,7 @@ final class ViewerGridView: NSView {
2020 // and render nothing but the black cell background.
2121 for c in cells.values { c.ctx = ctx }
2222 fusionCell?.ctx = ctx
23 subtitles.ctx = ctx
2324 }
2425 }
2526 private var store: Store { ctx.store }
......@@ -46,6 +47,11 @@ final class ViewerGridView: NSView {
4647 /// one-click way back). Only shown when there are no panes at all.
4748 private let placeholder = ViewerPlaceholder()
4849
50 /// Synchronized clover-transcript captions, drawn on top of the cells.
51 /// Positions itself (via `preferredFrame`) but hides itself unless a
52 /// transcript-bearing clip is under the playhead and captions are enabled.
53 private let subtitles = SubtitleOverlay()
54
4955 override init(frame: NSRect) {
5056 super.init(frame: frame)
5157 wantsLayer = true
......@@ -76,6 +82,11 @@ final class ViewerGridView: NSView {
7682 placeholder.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 16),
7783 placeholder.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -16),
7884 ])
85 // Captions ride on top of the cells. Added last so later cells (inserted
86 // `.below`) stay under it; positioned in `layout()`.
87 subtitles.ctx = ctx
88 addSubview(subtitles)
89
7990 // Dropping media anywhere in the empty viewer imports it (delegating to
8091 // the timeline's importer), so the "Drag files…" prompt is real.
8192 registerForDraggedTypes([.fileURL])
......@@ -304,6 +315,12 @@ final class ViewerGridView: NSView {
304315 override func layout() {
305316 super.layout()
306317 applyFrames(animated: false, appearing: [])
318 // Keep the caption box centred near the bottom and above every cell.
319 subtitles.frame = subtitles.preferredFrame(in: bounds)
320 if subviews.last !== subtitles {
321 subtitles.removeFromSuperview()
322 addSubview(subtitles)
323 }
307324 }
308325
309326 /// Lay out the panes and animate them into place. Normally a justified-rows
......@@ -967,11 +984,33 @@ final class ViewerCell: ViewerCellBase {
967984 /// those flash; instead we wait out a short grace period and only reveal it
968985 /// if the cell is still empty. Any resolved frame cancels it.
969986 private var pendingSpinner: DispatchWorkItem?
970 private func scheduleLoadingOverlay() {
971 guard !overlayVisible, pendingSpinner == nil else { return }
987 private var overlayText: String?
988
989 // Miss metrics: every spinner that actually shows is one recorded "miss",
990 // logged with a diagnosis of WHY the frame wasn't ready (see
991 // ChunkManager.missDiagnosis) and, on recovery, how long it lasted —
992 // so "I saw the loading screen" is answerable from the log.
993 private static var missCount = 0
994 private static var missSeconds = 0.0
995 private var missStart: Date?
996
997 private func scheduleLoadingOverlay(media: MediaItem?, clip: Clip?) {
998 guard overlayText == nil, pendingSpinner == nil else { return }
972999 let work = DispatchWorkItem { [weak self] in
973 self?.pendingSpinner = nil
974 self?.showLoadingOverlay(true)
1000 guard let self else { return }
1001 self.pendingSpinner = nil
1002 self.showOverlay("Loading Media…", spinning: true)
1003 self.missStart = Date()
1004 Self.missCount += 1
1005 if let media, let clip {
1006 let src = max(0, clip.sourceTime(at: self.playback.playhead))
1007 SeqLog.log("[miss] spinner ON %@ src=%.1f %@ (miss #%d)",
1008 media.displayName, src,
1009 self.chunks.missDiagnosis(media: media, sourceTime: src),
1010 Self.missCount)
1011 } else {
1012 SeqLog.log("[miss] spinner ON (no clip resolved) (miss #%d)", Self.missCount)
1013 }
9751014 }
9761015 pendingSpinner = work
9771016 DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: work)
......@@ -979,15 +1018,38 @@ final class ViewerCell: ViewerCellBase {
9791018 private func cancelLoadingOverlay() {
9801019 pendingSpinner?.cancel()
9811020 pendingSpinner = nil
982 showLoadingOverlay(false)
983 }
984
985 private func showLoadingOverlay(_ show: Bool) {
986 if show { layoutOverlay(in: bounds) }
987 guard overlayVisible != show else { return }
988 overlayVisible = show
989 overlayBg.isHidden = !show
990 if show {
1021 if let start = missStart {
1022 missStart = nil
1023 let d = Date().timeIntervalSince(start)
1024 Self.missSeconds += d
1025 SeqLog.log("[miss] spinner OFF after %.1fs (session: %d misses, %.0fs total)",
1026 d, Self.missCount, Self.missSeconds)
1027 }
1028 hideOverlay()
1029 }
1030
1031 /// A persistent (non-transient) overlay — media offline / failed to decode.
1032 /// No debounce and no spinner: it's a steady state, not a "wait a moment."
1033 private func showStateOverlay(_ text: String) {
1034 pendingSpinner?.cancel(); pendingSpinner = nil
1035 if let start = missStart { // spinner resolved INTO a steady state
1036 missStart = nil
1037 let d = Date().timeIntervalSince(start)
1038 Self.missSeconds += d
1039 SeqLog.log("[miss] spinner OFF after %.1fs → %@", d, text)
1040 }
1041 showOverlay(text, spinning: false)
1042 }
1043
1044 private func showOverlay(_ text: String, spinning: Bool) {
1045 guard overlayText != text else { return } // already showing this
1046 overlayText = text
1047 overlayVisible = true
1048 layoutOverlay(in: bounds)
1049 loadingText.string = text
1050 spinnerLayer.isHidden = !spinning
1051 overlayBg.isHidden = false
1052 if spinning {
9911053 if spinnerLayer.animation(forKey: "spin") == nil {
9921054 let a = CABasicAnimation(keyPath: "transform.rotation.z")
9931055 a.fromValue = 0
......@@ -1000,6 +1062,13 @@ final class ViewerCell: ViewerCellBase {
10001062 spinnerLayer.removeAnimation(forKey: "spin")
10011063 }
10021064 }
1065 private func hideOverlay() {
1066 guard overlayText != nil else { return }
1067 overlayText = nil
1068 overlayVisible = false
1069 overlayBg.isHidden = true
1070 spinnerLayer.removeAnimation(forKey: "spin")
1071 }
10031072
10041073 private var currentClipId: UUID?
10051074
......@@ -1285,17 +1354,54 @@ final class ViewerCell: ViewerCellBase {
12851354 && frontLayer.isReadyForDisplay
12861355 && onTime
12871356 var status = covered ? "" : "processing…"
1357 if !itemOK {
1358 // The player can't show this moment yet: start decoding the real
1359 // frame into the RAM cache now (from a chunk, rescue slice, or
1360 // playable original) so the next update can stand in with it —
1361 // exact where the filmstrip is a 240px thumb. Self-deduping.
1362 FrameCache.shared.warm(mediaKey: media.cacheKey, at: src,
1363 source: chunks.frameSource(media: media, sourceTime: src))
1364 }
12881365 if itemOK {
12891366 frontLayer.isHidden = false
12901367 imageLayer.isHidden = true
12911368 cancelLoadingOverlay()
1369 } else if let frame = FrameCache.shared.image(media: media, at: src) {
1370 // The RAM frame cache has the exact (full-quality) frame for this
1371 // moment — a warmed cut boundary, or a stand-in decoded on demand
1372 // below. Better than the 240px filmstrip thumb, and it makes the
1373 // hold across an item swap invisible.
1374 frontLayer.isHidden = true
1375 imageLayer.isHidden = false
1376 imageLayer.contents = frame
1377 cancelLoadingOverlay()
1378 if MediaPipeline.shared.isOffline(media) { status = "offline" }
12921379 } else if let strip = MediaPipeline.shared.filmstripImage(for: media, at: src) {
12931380 // A filmstrip is a real frame for this moment: stand in with it so
1294 // the transition is filmstrip → video, never black.
1381 // the transition is filmstrip → video, never black. If the original
1382 // is offline the cached strip still previews, but flag it in the
1383 // corner so it's clear playback/export won't work until it's back.
12951384 frontLayer.isHidden = true
12961385 imageLayer.isHidden = false
12971386 imageLayer.contents = strip
12981387 cancelLoadingOverlay()
1388 if MediaPipeline.shared.isOffline(media) { status = "offline" }
1389 } else if MediaPipeline.shared.isOffline(media) {
1390 // The original file is gone (unmounted NAS / moved). Don't imply it's
1391 // loading — say so plainly, and don't spin forever.
1392 frontLayer.isHidden = true
1393 imageLayer.isHidden = true
1394 imageLayer.contents = nil
1395 showStateOverlay("Media Offline")
1396 status = "offline"
1397 } else if chunks.buildFailed(media: media, sourceTime: src) {
1398 // The proxy chunk hard-failed and the original isn't playable — this
1399 // frame genuinely can't be shown; surface it instead of spinning.
1400 frontLayer.isHidden = true
1401 imageLayer.isHidden = true
1402 imageLayer.contents = nil
1403 showStateOverlay("Can't Decode")
1404 status = "failed"
12991405 } else {
13001406 // No live video AND no stand-in. This is usually just a transient
13011407 // (an item swap or a seek that lands within a few frames), so do
......@@ -1303,7 +1409,7 @@ final class ViewerCell: ViewerCellBase {
13031409 // on screen and only escalate to the framed "Loading Media…"
13041410 // overlay if the empty state actually persists (see
13051411 // scheduleLoadingOverlay). That kills the paused spinner flashes.
1306 scheduleLoadingOverlay()
1412 scheduleLoadingOverlay(media: media, clip: clip)
13071413 status = ""
13081414 }
13091415 setStatus(status)
sequencer/Sources/Sequencer/WindowController.swift+16-6
......@@ -167,11 +167,23 @@ final class SequencerWindowController: NSWindowController, NSWindowDelegate,
167167 }
168168 }
169169
170 /// This project became frontmost — now (and only now) kick the whole-project
171 /// proxy pre-build so scrubbing anywhere is smooth. Deferring the fill to here
172 /// rather than document load is what stops state-restored *background* projects
173 /// from all transcoding their full proxy sets at once and overflowing the
174 /// shared cache (see DocumentContext.startServices). `ensure` is idempotent —
175 /// already-built chunks are skipped — so re-focusing a project is essentially
176 /// free. Ignore the popout window becoming main (it shares this ctx).
177 func windowDidBecomeMain(_ notification: Notification) {
178 guard (notification.object as? NSWindow) === window else { return }
179 ctx.chunks.ensure(for: ctx.store.project)
180 }
181
170182 // MARK: - Menu validation (per-document items)
171183
172184 func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
173185 switch menuItem.action {
174 case #selector(undo): return store.canUndo || StoryboardEditor.shared.canUndoRaster
186 case #selector(undo): return store.canUndo
175187 case #selector(redo): return store.canRedo
176188 case #selector(deleteSelected), #selector(rippleDeleteSelected):
177189 return !store.selection.isEmpty
......@@ -211,11 +223,9 @@ final class SequencerWindowController: NSWindowController, NSWindowDelegate,
211223
212224 // MARK: - Edit / Clip actions
213225
214 @objc func undo() {
215 if StoryboardEditor.shared.undoRasterIfKey() { return }
216 if session.mainTool.isDraw, ctx.boards.undoLastStroke() { return }
217 store.undo()
218 }
226 // Drawing edits now live on the same undo timeline as model edits, so ⌘Z /
227 // ⌘⇧Z route through the Store regardless of which window is focused.
228 @objc func undo() { store.undo() }
219229 @objc func redo() { store.redo() }
220230 @objc func deselectAll() {
221231 store.selection = []
sequencer/Sources/Sequencer/main.swift+43
......@@ -6,16 +6,59 @@ if CommandLine.arguments.count >= 3, CommandLine.arguments[1] == "--selftest" {
66 exit(0)
77}
88
9// Headless cache-budget test: sequencer --cachetest <file.sq> [capGB]
10if CommandLine.arguments.count >= 3, CommandLine.arguments[1] == "--cachetest" {
11 let cap = CommandLine.arguments.count >= 4 ? Int(CommandLine.arguments[3]) ?? 0 : 0
12 runCacheTest(path: CommandLine.arguments[2], capGB: cap)
13 exit(0)
14}
15
916if CommandLine.arguments.contains("--uitest") {
1017 _ = NSApplication.shared // AppKit needs an app instance for views/windows
1118 MainActor.assumeIsolated { runUITest() }
1219}
1320
21// Diagnostic: bare AVPlayerLayer window on a stitched composition, using the
22// app's own bundle/signing — isolates layer rendering from the app machinery.
23if CommandLine.arguments.count >= 3, CommandLine.arguments[1] == "--layertest" {
24 let app = NSApplication.shared
25 app.setActivationPolicy(.regular)
26 MainActor.assumeIsolated {
27 runLayerTest(specs: Array(CommandLine.arguments.dropFirst(2)))
28 }
29 app.run()
30}
31
1432if CommandLine.arguments.count >= 3, CommandLine.arguments[1] == "--perftest" {
1533 _ = NSApplication.shared
1634 MainActor.assumeIsolated { runPerfTest(path: CommandLine.arguments[2]) }
1735}
1836
37// pkill/SIGTERM (run.sh does one per rebuild) must also take the ffmpeg
38// children down — the default handler kills only this process, minting the
39// orphan encoders that exhaust VideoToolbox and black out the next launch.
40// A background queue, NOT main: a wedged main thread must not make the app
41// unkillable (pkill looked ignored while main was frozen).
42signal(SIGTERM, SIG_IGN)
43signal(SIGINT, SIG_IGN)
44let signalQueue = DispatchQueue(label: "sequencer.signals", qos: .userInteractive)
45let termSource = DispatchSource.makeSignalSource(signal: SIGTERM, queue: signalQueue)
46let intSource = DispatchSource.makeSignalSource(signal: SIGINT, queue: signalQueue)
47for src in [termSource, intSource] {
48 src.setEventHandler {
49 MediaPipeline.terminateChildren()
50 exit(0)
51 }
52 src.resume()
53}
54
55HangMonitor.start()
56// SEQ_HANGTEST=1: deliberately stall the main thread once, to verify the
57// watchdog end-to-end (a [hang] line with this frame should hit the log).
58if ProcessInfo.processInfo.environment["SEQ_HANGTEST"] != nil {
59 DispatchQueue.main.asyncAfter(deadline: .now() + 2) { hangTestStall() }
60}
61
1962let app = SeqApplication.shared
2063let delegate = AppDelegate()
2164app.delegate = delegate
sequencer/build.sh+3-3
......@@ -81,7 +81,7 @@ cat > "$APP/Contents/Info.plist" <<'PLIST'
8181 <key>CFBundleExecutable</key>
8282 <string>Sequencer</string>
8383 <key>CFBundleIdentifier</key>
84 <string>com.clover.Sequencer</string>
84 <string>net.paperclover.Sequencer</string>
8585 <key>CFBundlePackageType</key>
8686 <string>APPL</string>
8787 <key>CFBundleShortVersionString</key>
......@@ -109,7 +109,7 @@ cat > "$APP/Contents/Info.plist" <<'PLIST'
109109 <string>Sequencer.ProjectDocument</string>
110110 <key>LSItemContentTypes</key>
111111 <array>
112 <string>com.clover.sequencer.project</string>
112 <string>net.paperclover.sequencer.project</string>
113113 </array>
114114 <key>CFBundleTypeExtensions</key>
115115 <array>
......@@ -121,7 +121,7 @@ cat > "$APP/Contents/Info.plist" <<'PLIST'
121121 <array>
122122 <dict>
123123 <key>UTTypeIdentifier</key>
124 <string>com.clover.sequencer.project</string>
124 <string>net.paperclover.sequencer.project</string>
125125 <key>UTTypeDescription</key>
126126 <string>Sequencer Project</string>
127127 <key>UTTypeConformsTo</key>
sequencer/run.sh+13-2
......@@ -1,8 +1,19 @@
11#!/bin/sh
2# Build and relaunch the app. Ships the RELEASE binary — the timeline is
3# measurably 4× slower under -Onone, so the app people actually run must be
4# optimized. Pass --debug to ship the debug build (e.g. when chasing a crash
5# with full assertions/symbols).
26set -e
37cd "$(dirname "$0")"
4swift build
8CONF=release
9[ "$1" = "--debug" ] && CONF=debug
10swift build -c "$CONF"
511pkill -x Sequencer 2>/dev/null || true
6cp .build/debug/Sequencer Sequencer.app/Contents/MacOS/Sequencer
12# The app terminates its ffmpeg children on SIGTERM, but sweep any strays from
13# older builds anyway — orphaned encoders exhaust the shared VideoToolbox
14# session pool and the next launch's players silently render black.
15sleep 1
16pkill -9 -f "Library/Caches/Sequencer" 2>/dev/null || true
17cp ".build/$CONF/Sequencer" Sequencer.app/Contents/MacOS/Sequencer
718codesign --force --deep --sign "Sequencer Dev" Sequencer.app
819open Sequencer.app