Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix chunk indexing #90

Merged
merged 7 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 67 additions & 4 deletions rem/DB.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ class DatabaseManager {
private let uniqueAppNames = Table("unique_application_names")

let allText = VirtualTable("allText")
let chunksFramesView = View("chunks_frames_view")

private let id = Expression<Int64>("id")
private let offsetIndex = Expression<Int64>("offsetIndex")
private let chunkId = Expression<Int64>("chunkId")
private let chunksFramesIndex = Expression<Int64>("chunksFramesIndex")
private let timestamp = Expression<Date>("timestamp")
private let filePath = Expression<String>("filePath")
private let activeApplicationName = Expression<String?>("activeApplicationName")
Expand All @@ -43,6 +45,7 @@ class DatabaseManager {
private var currentChunkId: Int64 = 0 // Initialize with a default value
private var lastFrameId: Int64 = 0
private var currentFrameOffset: Int64 = 0
private var lastChunksFramesIndex: Int64 = 0

init() {
if let savedir = RemFileManager.shared.getSaveDir() {
Expand All @@ -54,6 +57,7 @@ class DatabaseManager {
createTables()
currentChunkId = getCurrentChunkId()
lastFrameId = getLastFrameId()
lastChunksFramesIndex = getLastChunksFramesIndex()
}

func purge() {
Expand Down Expand Up @@ -116,6 +120,26 @@ class DatabaseManager {

// Text search
try! db.run(allText.create(.FTS4(config), ifNotExists: true))

// Create chunksFramesView (ensures all frames have associated chunks)
let viewSQL = """
CREATE VIEW IF NOT EXISTS chunks_frames_view AS
SELECT
ROW_NUMBER() OVER (ORDER BY vc.id, f.id) as chunksFramesIndex,
vc.id as chunkId,
vc.filePath,
f.id as frameId,
f.timestamp,
f.activeApplicationName,
f.offsetIndex
FROM
video_chunks vc
JOIN
frames f ON vc.id = f.chunkId
ORDER BY
vc.id, f.id;
"""
try! db.run(viewSQL)
}

private func createIndices() {
Expand All @@ -124,6 +148,8 @@ class DatabaseManager {
try db.run(frames.createIndex(chunkId, id, unique: false, ifNotExists: true))
try db.run(frames.createIndex(timestamp, ifNotExists: true))

// For speeding up chunksFramesView
try db.run(videoChunks.createIndex(id, unique: true, ifNotExists: true))
// Additional indices can be added here as needed
} catch {
print("Failed to create indices: \(error)")
Expand All @@ -132,8 +158,8 @@ class DatabaseManager {

private func getCurrentChunkId() -> Int64 {
do {
if let lastChunk = try db.pluck(videoChunks.order(id.desc)) {
return lastChunk[id] + 1
if let lastFrame = try db.pluck(frames.order(id.desc)) {
return lastFrame[chunkId] + 1
}
} catch {
print("Error fetching last chunk ID: \(error)")
Expand All @@ -147,17 +173,29 @@ class DatabaseManager {
return lastFrame[id]
}
} catch {
print("Error fetching last chunk ID: \(error)")
print("Error fetching last frame ID: \(error)")
}
return 0
}

private func getLastChunksFramesIndex() -> Int64 {
do {
if let lastFrame = try db.pluck(chunksFramesView.order(chunksFramesIndex.desc)) {
return lastFrame[chunksFramesIndex]
}
} catch {
print("Error fetching last chunks frames Index: \(error)")
}
return 0
}

// Insert a new video chunk and return its ID
func startNewVideoChunk(filePath: String) -> Int64 {
let insert = videoChunks.insert(self.filePath <- filePath)
let insert = videoChunks.insert(self.id <- currentChunkId, self.filePath <- filePath)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could remove auto increment but I don't see the harm in keeping the requirements of increasing and unique

let id = try! db.run(insert)
currentChunkId = id + 1
currentFrameOffset = 0
lastChunksFramesIndex = getLastChunksFramesIndex()
return id
}

Expand Down Expand Up @@ -208,6 +246,19 @@ class DatabaseManager {
if let frame = try db.pluck(query) {
return (frame[offsetIndex], frame[filePath])
}
} catch {
return nil
}

return nil
}

func getFrameByChunksFramesIndex(forIndex index: Int64) -> (offsetIndex: Int64, filePath: String)? {
do {
let query = chunksFramesView.filter(chunksFramesIndex == index).limit(1)
if let frame = try db.pluck(query) {
return (frame[offsetIndex], frame[filePath])
}

// let justFrameQuery = frames.filter(frames[id] === index).limit(1)
// try! db.run(justFrameQuery.delete())
Expand Down Expand Up @@ -283,6 +334,10 @@ class DatabaseManager {
return lastFrameId
}

func getMaxChunksFramesIndex() -> Int64 {
return lastChunksFramesIndex
}

func getLastAccessibleFrame() -> Int64 {
do {
let query = frames.join(videoChunks, on: chunkId == videoChunks[id]).select(frames[id]).order(frames[id].desc).limit(1)
Expand Down Expand Up @@ -387,6 +442,14 @@ class DatabaseManager {
return extractFrame(from: videoURL, frameOffset: frameData.offsetIndex, maxSize: maxSize)
}

func getImageByChunksFramesIndex(index: Int64, maxSize: CGSize? = nil) -> CGImage? {
guard let frameData = DatabaseManager.shared.getFrameByChunksFramesIndex(forIndex: index) else { return nil }

let videoURL = URL(fileURLWithPath: frameData.filePath)
print(frameData.filePath, frameData.offsetIndex)
return extractFrame(from: videoURL, frameOffset: frameData.offsetIndex, maxSize: maxSize)
}

func getImages(filePath: String, frameOffsets: [Int64], maxSize: CGSize? = nil) -> AVAssetImageGenerator.Images {
let videoURL = URL(fileURLWithPath: filePath)
return extractFrames(from: videoURL, frameOffsets: frameOffsets, maxSize: maxSize)
Expand Down
13 changes: 7 additions & 6 deletions rem/TimelineView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ struct TimelineView: View {
var body: some View {
ZStack {
let frame = NSScreen.main?.frame ?? NSRect.zero
let image = DatabaseManager.shared.getImage(index: viewModel.currentFrameIndex)
let image = DatabaseManager.shared.getImageByChunksFramesIndex(index: viewModel.currentFrameIndex)
let nsImage = image.flatMap { NSImage(cgImage: $0, size: NSSize(width: $0.width, height: $0.height)) }

CustomHostingControllerRepresentable(
Expand Down Expand Up @@ -74,7 +74,7 @@ struct TimelineView: View {

private func analyzeImage(index: Int64) {
Task {
if let image = DatabaseManager.shared.getImage(index: index) {
if let image = DatabaseManager.shared.getImageByChunksFramesIndex(index: index) {
let configuration = ImageAnalyzer.Configuration([.text])
do {
let analysis = try await imageAnalyzer.analyze(image, orientation: CGImagePropertyOrientation.up, configuration: configuration)
Expand Down Expand Up @@ -290,7 +290,7 @@ class TimelineViewModel: ObservableObject {
private var indexUpdateThrottle = Throttler(delay: 0.05)

init() {
let maxFrame = DatabaseManager.shared.getMaxFrame()
let maxFrame = DatabaseManager.shared.getMaxChunksFramesIndex()
currentFrameIndex = maxFrame
currentFrameContinuous = Double(maxFrame)
}
Expand All @@ -299,21 +299,21 @@ class TimelineViewModel: ObservableObject {
// Logic to update the index based on the delta
// This method will be called from AppDelegate
let nextValue = currentFrameContinuous - delta * speedFactor
let maxValue = Double(DatabaseManager.shared.getMaxFrame())
let maxValue = Double(DatabaseManager.shared.getMaxChunksFramesIndex())
let clampedValue = min(max(1, nextValue), maxValue)
self.currentFrameContinuous = clampedValue
self.updateIndexSafely()
}

func updateIndex(withIndex: Int64) {
let maxValue = Double(DatabaseManager.shared.getMaxFrame())
let maxValue = Double(DatabaseManager.shared.getMaxChunksFramesIndex())
let clampedValue = min(max(1, Double(withIndex)), maxValue)
self.currentFrameContinuous = clampedValue
self.updateIndexSafely()
}

func setIndexToLatest() {
let maxFrame = DatabaseManager.shared.getMaxFrame()
let maxFrame = DatabaseManager.shared.getMaxChunksFramesIndex()
DispatchQueue.main.async {
self.currentFrameContinuous = Double(maxFrame)
self.currentFrameIndex = maxFrame
Expand All @@ -324,6 +324,7 @@ class TimelineViewModel: ObservableObject {
indexUpdateThrottle.throttle {
let rounded = Int64(self.currentFrameContinuous)
self.currentFrameIndex = rounded
print(self.currentFrameIndex)
}
}

Expand Down
15 changes: 12 additions & 3 deletions rem/remApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ func drawStatusBarIcon(rect: CGRect) -> Bool {

if event.scrollingDeltaY < 0 && !isTimelineOpen() { // Check if scroll up
DispatchQueue.main.async { [weak self] in
self?.showTimelineView(with: DatabaseManager.shared.getMaxFrame())
self?.showTimelineView(with: DatabaseManager.shared.getMaxChunksFramesIndex())
}
}
}
Expand Down Expand Up @@ -483,8 +483,6 @@ func drawStatusBarIcon(rect: CGRect) -> Bool {
if let savedir = RemFileManager.shared.getSaveDir() {
let outputPath = savedir.appendingPathComponent("output-\(Date().timeIntervalSince1970).mp4").path

let _ = DatabaseManager.shared.startNewVideoChunk(filePath: outputPath)

// Setup the FFmpeg process for the chunk
let ffmpegProcess = Process()
let bundleURL = Bundle.main.bundleURL
Expand Down Expand Up @@ -531,6 +529,17 @@ func drawStatusBarIcon(rect: CGRect) -> Bool {

// Close the pipe and handle the process completion
ffmpegInputPipe.fileHandleForWriting.closeFile()
ffmpegProcess.waitUntilExit()

// Check if FFmpeg process completed successfully
if ffmpegProcess.terminationStatus == 0 {
// Start new video chunk in database only if FFmpeg succeeds
let _ = DatabaseManager.shared.startNewVideoChunk(filePath: outputPath)
logger.info("Video successfully saved and registered.")
} else {
logger.error("FFmpeg failed to process video chunk.")
}


// Read FFmpeg's output and error
let outputData = ffmpegOutputPipe.fileHandleForReading.readDataToEndOfFile()
Expand Down