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

feat: add timeline fps in api request #760

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion content/docs/NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,4 @@ Abort trap: 6
<summary>How can I interpret my data more intuitively?</summary>

We recommend using [TablePlus](https://tableplus.com/) to open the SQLite database located alongside the data.
</details>
</details>
6 changes: 4 additions & 2 deletions screenpipe-app-tauri/app/timeline/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,18 @@ export default function Timeline() {
}, []);

const setupEventSource = () => {
console.log("timeline fps:", settings.timelineFps);
if (eventSourceRef.current) {
eventSourceRef.current.close();
}

const target_fps = settings.timelineFps;
const endTime = new Date();
endTime.setMinutes(endTime.getMinutes() - 2);
const startTime = new Date();
startTime.setHours(0, 1, 0, 0);

const url = `http://localhost:3030/stream/frames?start_time=${startTime.toISOString()}&end_time=${endTime.toISOString()}&order=descending`;
const url = `http://localhost:3030/stream/frames?start_time=${startTime.toISOString()}&end_time=${endTime.toISOString()}&order=descending&target_fps=${target_fps}`;

setLoadedTimeRange({
start: startTime,
Expand Down Expand Up @@ -181,7 +183,7 @@ export default function Timeline() {
setIsLoading(false);
setError(null);
};
}, []);
}, [settings.timelineFps]);

const handleScroll = useMemo(
() =>
Expand Down
44 changes: 43 additions & 1 deletion screenpipe-app-tauri/components/recording-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export function RecordingSettings({
> | null>(null);
const [windowsForIgnore, setWindowsForIgnore] = useState("");
const [windowsForInclude, setWindowsForInclude] = useState("");
const [isTimelineEnabled, setIsTimelineEnabled] = useState(localSettings.enableFrameCache);

const [availableMonitors, setAvailableMonitors] = useState<MonitorDevice[]>(
[]
Expand Down Expand Up @@ -229,6 +230,7 @@ export function RecordingSettings({
includedWindows: localSettings.includedWindows,
deepgramApiKey: localSettings.deepgramApiKey,
fps: localSettings.fps,
timelineFps: localSettings.timelineFps,
vadSensitivity: localSettings.vadSensitivity,
audioChunkDuration: localSettings.audioChunkDuration,
analyticsEnabled: localSettings.analyticsEnabled,
Expand Down Expand Up @@ -384,6 +386,10 @@ export function RecordingSettings({
setLocalSettings({ ...localSettings, fps: value[0] });
};

const handleTimelineFpsChange = (value: number[]) => {
setLocalSettings({ ...localSettings, timelineFps: value[0] });
};

const handleVadSensitivityChange = (value: number[]) => {
const sensitivityMap: { [key: number]: VadSensitivity } = {
2: "high",
Expand Down Expand Up @@ -583,6 +589,7 @@ export function RecordingSettings({
};

const handleFrameCacheToggle = (checked: boolean) => {
setIsTimelineEnabled(checked);
setLocalSettings({
...localSettings,
enableFrameCache: checked,
Expand Down Expand Up @@ -1469,7 +1476,7 @@ export function RecordingSettings({
<div className="flex items-center space-x-2">
<Switch
id="frame-cache-toggle"
checked={localSettings.enableFrameCache}
checked={isTimelineEnabled}
onCheckedChange={handleFrameCacheToggle}
/>
<Label
Expand All @@ -1496,6 +1503,41 @@ export function RecordingSettings({
</div>
</div>
</div>
{isTimelineEnabled && (
<div className="flex !w-full items-center space-x-4">
<Slider
id="timeline-fps"
min={0.1}
max={10}
step={0.1}
value={[localSettings.timelineFps]}
onValueChange={handleTimelineFpsChange}
className="flex-grow"
/>
<span className="w-12 text-right">
{localSettings.timelineFps.toFixed(1)}
</span>
<Label
htmlFor="timeline-fps"
className="flex items-center space-x-2"
>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<HelpCircle className="h-4 w-4 cursor-default" />
</TooltipTrigger>
<TooltipContent side="right">
<p>
timeline fps tuner, which will let you set fps for timeline stream
<br />
may increase CPU usage and memory consumption.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
</div>
)}
{isMacOS && (
<div className="flex items-center space-x-2">
<Switch
Expand Down
4 changes: 3 additions & 1 deletion screenpipe-app-tauri/lib/hooks/use-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export type Settings= {
aiUrl: string;
aiMaxContextChars: number;
fps: number;
timelineFps: number;
vadSensitivity: VadSensitivity;
analyticsEnabled: boolean;
audioChunkDuration: number; // new field
Expand Down Expand Up @@ -94,6 +95,7 @@ const DEFAULT_SETTINGS: Settings = {
aiUrl: "https://api.openai.com/v1",
aiMaxContextChars: 30000,
fps: 0.5,
timelineFps: 0.1,
vadSensitivity: "high",
analyticsEnabled: true,
audioChunkDuration: 30, // default to 10 seconds
Expand Down Expand Up @@ -299,4 +301,4 @@ async function createUserSettings(
}

return userSettingsObject as Settings
}
}
5 changes: 2 additions & 3 deletions screenpipe-server/src/bin/screenpipe-server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -984,9 +984,8 @@ fn persist_path_windows(new_path: PathBuf) -> anyhow::Result<()> {

// Ensure 'setx' command can handle the new PATH length
if current_path.len() + new_path.to_str().unwrap_or("").len() + 1 > 1024 {
return Err(anyhow::anyhow!(
"the PATH is too long to persist using 'setx'. please shorten the PATH."
));
debug!("the PATH is too long to persist using 'setx'. please shorten the PATH.");
return Ok(());
}

// Construct the new PATH string
Expand Down
4 changes: 3 additions & 1 deletion screenpipe-server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,7 @@ enum Order {
pub struct StreamFramesRequest {
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
target_fps: Option<f64>,
// #[serde(rename = "order")]
// #[serde(default = "descending")]
// order: Order,
Expand Down Expand Up @@ -1370,9 +1371,10 @@ async fn stream_frames_handler(
// Spawn frame extraction task using get_frames
tokio::spawn({
let frame_tx = frame_tx.clone();
let target_fps = request.target_fps.unwrap_or(0.1);
async move {
tokio::select! {
result = cache.get_frames(center_timestamp, duration_minutes, frame_tx.clone(), true) => {
result = cache.get_frames(center_timestamp, duration_minutes, frame_tx.clone(), true, target_fps) => {
if let Err(e) = result {
error!("frame extraction failed: {}", e);
// Send error to client
Expand Down
10 changes: 7 additions & 3 deletions screenpipe-server/src/video_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ impl FrameCache {
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
frame_tx: FrameChannel,
target_fps: f64,
) -> Result<()> {
let mut extraction_queue = HashMap::new();
let mut total_frames = 0;
Expand Down Expand Up @@ -545,6 +546,7 @@ impl FrameCache {
tasks,
frame_tx.clone(),
self.cache_tx.clone(),
target_fps,
)
.await?;
total_frames += extracted;
Expand All @@ -561,6 +563,7 @@ impl FrameCache {
duration_minutes: i64,
frame_tx: Sender<TimeSeriesFrame>,
descending: bool,
target_fps: f64,
) -> Result<()> {
let start = timestamp - Duration::minutes(duration_minutes / 2);
let end = timestamp + Duration::minutes(duration_minutes / 2);
Expand All @@ -575,7 +578,7 @@ impl FrameCache {
let cache_clone = self.clone();
tokio::spawn(async move {
let result = cache_clone
.extract_frames_batch(start, end, extract_tx)
.extract_frames_batch(start, end, extract_tx, target_fps)
.await;
debug!("extraction task completed: {:?}", result.is_ok());
result
Expand Down Expand Up @@ -636,6 +639,7 @@ async fn extract_frame(
tasks: Vec<(FrameData, OCREntry)>,
frame_tx: FrameChannel,
cache_tx: mpsc::Sender<CacheMessage>,
target_fps: f64,
) -> Result<usize> {
if !is_video_file_complete(&ffmpeg, &video_file_path).await? {
debug!("skipping incomplete video file: {}", video_file_path);
Expand All @@ -655,11 +659,11 @@ async fn extract_frame(
let output_pattern = temp_dir.path().join("frame%d.jpg");

// Calculate frame interval based on target FPS
let frame_interval = (source_fps / 0.1).round() as i64; // Using 0.1 as target FPS
let frame_interval = (source_fps / target_fps).round() as i64; // Using 0.1 as target FPS

debug!(
"extracting frames with interval {} (source: {}fps, target: {}fps)",
frame_interval, source_fps, 0.1
frame_interval, source_fps, target_fps
);

// Calculate which frames to extract
Expand Down
Loading