Who Stole my cat's food?

the idea was to record all the time, and at THE EVENT
stop overwriting the files / copy the directory / make the movie …

  • that requires also to take the above RAM usage ideas
    images in RAM or files in RAM DISK, seriously
    as it could damage your drive

but you start the recording at THE EVENT
what will never give info about the time prior


your arduino code seems to also directly set record true/false
that would never allow any time frame thinking


also i tested here:

    //save(outfile);
    saveFrame(outfile);

same result.

i assume you not run my code as posted above?


also you could guess that i love cats too,
so can you post some photos of yours?

Here is stripping the concept down to almost the simplest possible ring buffer for saveFrame.

// https://discourse.processing.org/t/who-stole-my-cats-food/13895/6
int clip = 0;
int bufferCount = 6;

void setup() {
  frameRate(2);
  colorMode(HSB,90);
  strokeWeight(5);
}

void draw() {
  // draw something
  translate(frameCount*4%width, 0);
  stroke(frameCount%60,100,100);
  line(0,0,0,height);

  // save to a buffer file --
  // % is what loops across the files
  String frameName = str(clip)+"_"+str(frameCount % bufferCount);
  saveFrame(frameName+".png");
}

void keyReleased() {
  // advance the clip file name part --
  // this automatically "saves" the old buffer
  // because those files are no longer overwritten
  clip++;
}

This doesn’t rename the files on disk – it just keeps overwriting them in a ring, then switches to a new buffer when you trigger an event.

For the next step you can rename the files when you finalize the buffer (e.g. on the event) to order them correctly – although honestly, if you are checking pngs for rats you can probably just sort your file browser by date modified.

1 Like

Thank you.
I got it to work with serial input, I am just incrementing the folder name on that date with each serial event.

 if ( ArduCOM_Port.available() > 0 ) { 
    IncomingByte = ArduCOM_Port.read(); 
    println (IncomingByte);
    if (IncomingByte==1)
    {
      println("last: "+outfile);
      ClipIncr++;
      IncomingByte=0;
    }
  }

void recording() {
  if ( record ) {
    x++; 
    if ( x >= xend ) 
    {
      x = 0;
    }
    TimeStampFun();
    // println (TimeStamp);
    fn1 = nf(x, 4);
    outfile = ClipIncr+"_"+DateStamp+fn0+fn1+fn2;
    fill(0, 0, 0);
    rect(0, 0, 120, 45, 7);
    //textFont(font,16); 
    fill(w);
    text(outfile, 5, 15);
    text(DateStamp +" "+ TimeStamp, 5, 35);

    save(outfile);
    String[] info = {outfile, ""};
    saveStrings(pointfile, info);
  }
}

A couple of problems remain:

  1. The “Movie Maker” from tools will assemble a film based on the file sequence name. So I end up with a broken film. (If my event happen at frame 97, the movie maker will not regard that as the begging of the film if there is a frame 0)

  2. I would like the sketch to make me the film. I searched a lot. Everyone points to the tool. Is there a function that will take the pngs and make a .MOV?

Thanks everyone

Jeremy, I will study your suggestion later. Thanks

    if (IncomingByte==1)
    {
      println("last: "+outfile);
      ClipIncr++;
      IncomingByte=0;
    }

in your case ( combine my counter / jeremydouglass clip / your DateStamp // ideas )
you can just reset the x = 0; too, when you change the clip number,
so the movie starts with 000.

also a little MOD to the clip idea,
not use it as filename, use it as path? ( tested “saveFrame” makes the path automatically )


it does make the use of movie maker easier, but not solve the pointer info ( now inside its path ) problem


nevertheless, if xend is still 900
every THE EVENT creates 900 more files and your drive will get full.
and if there is no event you burn your drive ( SSD ? ) as you still
store to the sketch path?


with

nf(clip,3)
clip++

and clip unlimited can get max 1000 sub directories,
again you could limit your clips like

if ( clip++ >= 9 ) clip = 0;
x=0;

get only the last 9 event ( and the running directory )
nice but creates the same ring buffer problem again for the directories

  • you must look for the date of the pointer file to know whats the newest one.
// rev 0 make filename
// rev 1 make point file
// rev 2 try clip as path idea what fits to moviemaker
// rev 3 use new win10 ramdisk 
// https://www.youtube.com/watch?v=KvjH6p5pB5Q
// http://memory.dataram.com/products-and-services/software/ramdisk

String d0  = "R:/TEMP/";                   // use my new WIN 10 / RAMDISK ( with save / recover option so not loose files on reboot!)
//String d0  = "";                           // now use sketch path
int clip = 0, clipend = 3;                 // create max 4 subdirs
String p0  = "event"+nf(clip, 3)+"/";      // make a path
String fn0 = "snap";
int fps = 2;                               // 2 frames / pictures per sec 
int x = 0, xend = 9;                       // and max 10 files == 5 sec recording time
String fn1 = nf(x, 3);
String fn2 = ".png";
String outfile = p0+fn0+fn1+fn2;           // filename
String pointfile = p0+"pointer.txt";       // pointer file at same path
boolean record = true;                     // init mode RECORDING

void setup() {
  size(300, 100);
  println("recording: "+record);
  frameRate(fps);
}                                          // end function setup

void draw() {
  background(200, 200, 0);
  recording();
}                                          // end function draw

void recording() {                         // called from main draw()
  if ( record ) { 
    fn1 = nf(x, 3);
    if (  x++  >= xend ) x = 0;            // so we start with "000"
    p0  = "event"+nf(clip, 3)+"/";         // use clip counter sub dir
    outfile   = d0+p0+fn0+fn1+fn2;         // drive / path / file + counter + .filetype
    pointfile = d0+p0+"pointer.txt";       // pointerfile there too
    text(outfile, 0, 10);                  // test show filename in canvas ( to check in files later )
    //save(outfile);
    saveFrame(outfile);                    // save every frame
    String[] info = {outfile, ""};
    saveStrings(pointfile, info);          // and record that name in first line of a pointer file
  }
}                                          // end function recording

void mousePressed() {
  if ( record ) {                          // toggle recording
    record = false;
    println("last: "+outfile+" \nrecording: "+record+" ,for start recording must click again");
  } else { 
    record = true; 
    if (clip++ >= clipend ) clip = 0;      // increment and limit recorded clips ( path )
    x = 0;                                 // reset filename number
    println("recording: "+record);
  }
}                                          // end function mousePressed

KLL

Thanks again for your reply and the ideas.
You said
you can just reset the x = 0; too, when you change the clip number,
so the movie starts with 000.

This wont work. If I do that I will start at clip 000 but after x >= xend the recounting starts.
Say I dont have a trigger till 5 min later. By now the x count is at some random number (Say 48) . That should be my first frame.
The triggers starts recording all frames in a loop in a new folder.
But what do I have in the old folder? 900 frames numbered from 0 to 900. The last frame should be frame 49, when the event happened. That is the OLDEST frame in my folder but is not numbered accordingly.
All the other frames should be re-numbered accordingly.
Meaning Frame 49 = Frame 900, frame 48= frame 899, etc.

Is there a file naming library for Processing?
Is there a way to do this with framecount?

Where the framecount is in the the file name and files older than (frameCount-900) are deleted?

GoToLoop

Is there any useful info for me in those links.
I know what nf is .
I do not want to Prevent overwriting files.

Here is what I want . I think the import java.io.*; library would do it, but I can not find any reference.

I want to save in a directory a frame and name it snap and the frameCount number.
Then I want to delete the frames that are older than current frame minus 900 frames.
THis way when I make a film, I only do the last 30 seconds of what my camera captured.

Is there a command that would go to the SaveFrame directory and delete files based on name, or time of creation?
Thanks

I tried

  String fileName = dataPath("\5_17-9-2019data\snap0521.png");
  File f = new File(fileName);
  if (f.exists()) {
    f.delete();
    println("deleted:", fileName);
  }
  else
  println("NOT FOUND");
  
  
}

But it did not work. Processing wont even accept the directory description,
dataPath("\5_17-9-2019data\snap0521.png");

I was hoping to delete with every frame some of the old frames. This way the frame number is always increasing.

Please help, Jeremy, I got this syntax from you on a different topic.
THanks

back to the beginning:
-a- you have a trigger from arduino
you used to save pictures from that moment, BUT
you wanted pictures from the time prior to the trigger.

so i give you the idea of saving permanent pictures
( to a ram-disk ) in ring-buffer way to prevent filling up the drive.
for not need to check on the timestamp of the files later i had the idea of the pointer file.

-b- that ring buffer
need a logic so you can bring that pictures in to the right order
( at moment of stop storing, at the moment when you make the movie… )

-c- but lets put that please back for a moment as there is a
still one not addressed point we have to do first:

the post trigger saving.

now that and using arduino and video…
i all packed already here
http://kll.engineering-news.org/kllfusion01/articles.php?article_id=162
there the idea is that you can configure like 1/2 == 50/50% or 1/3 == 66/33%
time prior / post trigger picture saving.

i play on and put it on my server because it looked like you
stopped play on this question…

This is my attempt using SimpleDateFormat::format() + File::delete() + Queue::remove() to always keep max screenshots at 900: :star_struck:

/**
 * Queued Stamped Snapshots (v1.3)
 * GoToLoop (2019/Sep/17)
 * https://Discourse.Processing.org/t/who-stole-my-cats-food/13895/16
 */

import java.text.SimpleDateFormat;
import java.util.Date;

import java.util.Queue;
import java.util.ArrayDeque;

static final String PATTERN = "yyyy-MMM-dd.HH-mm-ss.", EXT = ".png";
static final int MAX_FILES = 900, FPS = 20;

final Queue<File> paths = new ArrayDeque<File>(MAX_FILES);

void setup() {
  size(400, 150);
  frameRate(FPS);
}

void draw() {
  background((color) random(#000000));
  makeSnapshot();
  displayTitle();
}

static final String getTimeStamp() {
  return new SimpleDateFormat(PATTERN).format(new Date());
}

void makeSnapshot() {
  if (paths.size() == MAX_FILES)  paths.remove().delete();

  final File newPath = dataFile(getTimeStamp() + frameCount + EXT);
  println(newPath);

  paths.add(newPath);
  saveFrame(newPath.getPath());
}

void displayTitle() {
  final String msg =
    "Frame: " + frameCount +
    "  -  Size: " + paths.size() + 
    "  -  FPS: " + round(frameRate);

  surface.setTitle(msg);
}

GoToLoop,
Thanks a lot, that is what I wanted. The oldest frames are dying and the new frames are being recorded.
So I implemented it with my USB cam and it works fine.

No to drive this home, I need to create a new save directory with each Serial event.
When the Arduino sends a message, I need to save these frames in a new directory so I can look back and see what happened 10 sec before that signal,

Here is what I tried

  if ( ArduCOM_Port.available() > 0 ) { 
    IncomingByte = ArduCOM_Port.read(); 
    println (IncomingByte);
    if (IncomingByte==1)
    {
    
      ClipIncr++;
      IncomingByte=0;
    }
  }

 
  
  makeSnapshot();
  displayTitle();
}

static final String getTimeStamp() {
  return new SimpleDateFormat(PATTERN).format(new Date());
}

void makeSnapshot() {
  if (paths.size() == MAX_FILES)  dataFile(paths.remove()).delete();

  final String newPath = dataPath(getTimeStamp() + frameCount + EXT);
  String ClipIncrStr=(nf(3,ClipIncr)); 
  
 // saveFrame(newPath);
  //paths.add(newPath);
  
  saveFrame(ClipIncrStr+"_"+newPath);
  paths.add(ClipIncrStr+"_"+newPath);
  //println (ClipIncr+"_"+newPath);
}

I was expecting to have a 0_data, 1_data etc after each Arduino trigger.
Instead nothing got recorded anymore.

What to do?
Thanks

Folder paths are separated w/ /, not “_”. :warning:

Thanks again,
I did try:

saveFrame(ClipIncr+"/"+newPath);
 paths.add(ClipIncr+"/"+newPath);

I do not get the “data” directory but I get directories named 0,1,2 etc increasing with each serial event.

The problem is, they are all empty, no frames are saved.

Any ideas? THANKS

PS
I did try the directories as strings, such as

`` String ClipIncrStr=(nf(ClipIncr,3));

saveFrame(ClipIncrStr+“/”+newPath);
paths.add(ClipIncrStr+“/”+newPath);
``

The directories are not even being made.
In red i get:

Error while saving image.
java.io.IOException: image save failed.
at processing.core.PImage.saveImageIO(PImage.java:3275)
at processing.core.PImage.save(PImage.java:3406)
at processing.core.PGraphics.save(PGraphics.java:8304)
at processing.core.PApplet.saveFrame(PApplet.java:3985)
at TimedRecording_05_FrameCount.makeSnapshot(TimedRecording_05_FrameCount.java:139)
at TimedRecording_05_FrameCount.draw(TimedRecording_05_FrameCount.java:122)
at processing.core.PApplet.handleDraw(PApplet.java:2476)
at processing.awt.PSurfaceAWT$12.callDraw(PSurfaceAWT.java:1547)
at processing.core.PSurfaceNone$AnimationThread.run(PSurfaceNone.java:313)

I’ve modified makeSnapshot() v1.3 to accept an extra String folder parameter: :prayer_beads:

void makeSnapshot() {
  makeSnapshot("");
}

void makeSnapshot(final String folder) {
  if (paths.size() == MAX_FILES)  paths.remove().delete();

  final String fold =
    folder == null || folder.isEmpty()? "" :
    folder.endsWith("/") || folder.endsWith("\\")? folder :
    folder + "/";

  final File newPath = dataFile(fold + getTimeStamp() + frameCount + EXT);
  println(newPath);

  paths.add(newPath);
  saveFrame(newPath.getPath());
}

Thanks for the help.
The weirdest thing happened. The 'delete" and “add” are not accepted anymore.

Also, it is not clear to me where would I plug the ClipIncrStr string?
Here is a snapshot.
Thanks

I’ve explicitly stated it was from the sketch’s version 1.3: :expressionless:

Where paths is a Queue of File objects: :file_cabinet:

Well, the overloaded makeSnapshot() got a String folder parameter for it. :framed_picture:

Thanks a lot, I think we’re getting close.

I ended up using
final String fold = ClipIncrStr+"/";

And things were nice, the files were being saved in a new directory with each serial event.
C:\Users\haral\Desktop\Timed_Recording\TimedRecording_05_FrameCount\data\004\2019-Sep-19.12-02-03.1685.png
C:\Users\haral\Desktop\Timed_Recording\TimedRecording_05_FrameCount\data\004\2019-Sep-19.12-02-03.1686.png
C:\Users\haral\Desktop\Timed_Recording\TimedRecording_05_FrameCount\data\004\2019-Sep-19.12-02-03.1687.png
C:\Users\haral\Desktop\Timed_Recording\TimedRecording_05_FrameCount\data\004\2019-Sep-19.12-02-04.1688.png
C:\Users\haral\Desktop\Timed_Recording\TimedRecording_05_FrameCount\data\004\2019-Sep-19.12-02-04.1689.png
C:\Users\haral\Desktop\Timed_Recording\TimedRecording_05_FrameCount\data\004\2019-Sep-19.12-02-04.1690.png

I got very happy till I figured that the files were being deleted from old directories too.
Meaning I was hoping to find in \data\004\ 200 files left intact because now I m recording in 005.
The sketch kills them all in all directories .

What to do?
Thanks

The sketch I’ve provided is mono-folder, just like you had requested originally: :roll_eyes:

As a workaround for this new multi-folder requirement, you can try out invoking clear() every time you change folder, so files from previous folders are never delete() anymore: :negative_squared_cross_mark:

Docs.Oracle.com/en/java/javase/11/docs/api/java.base/java/util/Collection.html#clear()

paths.clear()

Yey, that worked,

I just added it in the serial event:

  if ( ArduCOM_Port.available() > 0 ) { 
    IncomingByte = ArduCOM_Port.read(); 
    println (IncomingByte);
    if (IncomingByte==1)
    {
     // println("last: "+outfile);
      ClipIncr++;
      ClipIncrStr=(nf(ClipIncr,3)); 
      paths.clear();
      IncomingByte=0;
    }
  }

That is awesome. Never knew this file management system.
For mysterious reasons displayTitle function does not work anymore,
There is no stamp on the top of the pic. Would be nice to have.

I attached the whole sketch.


import java.io.File;

import java.text.SimpleDateFormat;
import java.util.Date;

import java.util.Queue;
import java.util.ArrayDeque;

static final String PATTERN = "yyyy-MMM-dd.HH-mm-ss.", EXT = ".png";
static final int MAX_FILES = 300, FPS = 30;


final Queue<File> paths = new ArrayDeque<File>(MAX_FILES);

import processing.video.*;
import java.awt.Font;
import java.awt.Rectangle;
PFont f;
PFont font;
int w= #FFFFFF; // white
int y= #FCF442; //yellow
int g= #A0FFA3; //green
int b= #64E1FF; // blue
int mg= #CC15D3; // Magenta
int o= #FF6C67;  // orange
int i= #B767FF; // indigo
int r= #FC0B03; // red
int bk = (#212121);  


int ClipIncr=0;
String ClipIncrStr="0";

import processing.serial.*;
Serial ArduCOM_Port; 

Capture cam;
int IncomingByte;


void setup() {
  size (640, 480, JAVA2D);
  font = loadFont("AgencyFB-Bold-200.vlw");

  String[] cameras = Capture.list();

  if (cameras == null) {
    println("Failed to retrieve the list of available cameras, will try the default...");
    cam = new Capture(this, 640, 480);
  } 
  if (cameras.length == 0) {
    println("There are no cameras available for capture.");
    exit();
  } else {
    println("Available cameras:");
    printArray(cameras);

    // The camera can be initialized directly using an element
    // from the array returned by list():
    cam = new Capture(this, cameras[7]);
    // Or, the settings can be defined based on the text in the list
    //cam = new Capture(this, 640, 480, "Built-in iSight", 30);

    // Start capturing the images from the camera
    cam.start();
  }
  frameRate(30);
  ArduCOM_Port = new Serial(this, "COM4", 115200);
  // record=false;
}  //end setup

void draw() {
  // background(bk); 
  if (cam.available() == true) {
    cam.read();
  }
  image(cam, 0, 0, 640, 480);

  if ( ArduCOM_Port.available() > 0 ) { 
    IncomingByte = ArduCOM_Port.read(); 
    println (IncomingByte);
    if (IncomingByte==1)
    {
     // println("last: "+outfile);
      ClipIncr++;
      ClipIncrStr=(nf(ClipIncr,3)); 
      paths.clear();
      IncomingByte=0;
    }
  }

 
  
  makeSnapshot();
  displayTitle();
}

static final String getTimeStamp() 
{
  return new SimpleDateFormat(PATTERN).format(new Date());
}

void makeSnapshot() {
  makeSnapshot("");
}

void makeSnapshot(final String folder) {
  
 
  if (paths.size() == MAX_FILES)  
  paths.remove().delete();
  
 

  final String fold = ClipIncrStr+"/";
   //   folder == null || folder.isEmpty()? "" :
   // folder.endsWith("/") || folder.endsWith("\\")? folder :
   // folder + "/";

  final File newPath = dataFile(fold + getTimeStamp() + frameCount + EXT);
  println(newPath);

  paths.add(newPath);
  saveFrame(newPath.getPath());
}


void displayTitle() {
  final String msg =
    "Frame: " + frameCount +
    "  -  Size: " + paths.size() + 
    "  -  FPS: " + round(frameRate);

  surface.setTitle(msg);
}