How can I merge video files without crashing my memory?

2 views (last 30 days)
I am trying to create a program that randomly chooses two seconds from a variable number of relatively short movie clips (short videos created for the purpose of putting together a '2 seconds a day' type movie). Everything is working fine, but I crash my systems memory after 8 videos or so, and I'm trying to put together around 100. I'm just learning how to program and my main problem revolves around how to free up memory. Below is my code.
if true
% code
files = dir( 'C:/Users/psychuser/Desktop/Pictures and Misc/Phone/*.mov' ); %finds all movies in a certain folder
n = 1;
for i= 1 : size( files , 1 );
originalfilename = sprintf( 'C:/Users/psychuser/Desktop/Pictures and Misc/Phone/%s' , files(i,1).name );%creates variable file name
obj = VideoReader( originalfilename ); %turns video into variable 'obj'
video = obj.read; %read video and properties into matlab file
x = round( obj.FrameRate ); %from that properties file turn framerate of video into variable
y = obj.NumberOfFrames; %from that properties file turn total number of frames into variable
z = ( y - ( x * 2 ) ); %z subtracts the frame rate from the total number of frames so that the new movie doesn't exceed the total number of frames
if ( x * 2 ) <= y %if total number of seconds is greater than 2 seconds choose random two seconds
j = ( n : (n + ( x * 2 ))); %j is a counter that knows where the last video ended and how big the current video is
startframe = randi ( z ); %choose random start point between first frame and last possible frame
choicerng = ( startframe : ( startframe + ( x * 2 ) ) );
video1 ( : , : , : , j ) = video( : , : , : , choicerng ); %merge new video to old 'combined' video
else % else choose the whole video
j = ( n : ( ( y - 1 ) + n ) ); %Y is rounding up for these videos creating a problem with a less than two second clip, to resolve this i subtracted 1 frame, needs a better fix
choicerng = ( 1 : y ); % choose whole video
video1 ( : , : , : , j ) = video( : , : , : , choicerng ); %merge new video to old 'combined' video
end
n = max(j) + 1; % last frame in the combined video
clear x y z originalfilename obj video choicerng startframe %clear variables
end %everything below here writes out the video to a new file called twoseconds
newfilename = 'C:/Users/psychuser/Desktop/Pictures and Misc/ChristiPhone/TwoSecond/twoseconds.avi';
writeobj = VideoWriter( newfilename );
open( writeobj );
writeVideo( writeobj , video1 );
close( writeobj );
clear all
end

Accepted Answer

Image Analyst
Image Analyst on 31 May 2015
Try not reading the whole video into memory. Use read() to read just the frames you want in.
  3 Comments
Image Analyst
Image Analyst on 3 Jun 2015
Wow, you're right. It's new but it didn't last long before being deprecated. It looks like they say use readFrame instead of read. That should be a simple fix (just renaming the method) and should resolve the issue, I would think.
It's always a dilemma when to change my demos. If I change read() to readFrame() then people with old versions of MATLAB won't be able to run my demo.
In some ways readFrame() looks worse than read() because with read() you could specify what frame number to extract whereas it doesn't look like readFrame has tha option.
Walter Roberson
Walter Roberson on 3 Jun 2015
Yes, you have to set the object properties to indicate which frame you want to read next. A clumsy system.

Sign in to comment.

More Answers (2)

Walter Roberson
Walter Roberson on 31 May 2015
See http://www.mathworks.com/help/matlab/import_export/read-video-files.html for how to use the readFrame method of VideoReader; it has an example of setting the current time and reading until a target time. You would not need to calculate the number of frames.
Also instead of storing the frames in memory and writing everything out at the same time, you can use VideoWriter to write individual frames.
You could use a procedure such as
open your output with videowriter
for 1 to number of videos to subset
pick a random video
open a reader onto the video
ask how long it is in seconds
if it is less than 2 seconds set lower time bound to 0 and upper time bound to actual length
otherwise pick a random floating point number between 0 and length minus 2 seconds, set lower time bound to that value and upper time bound to 2 seconds later
set reader start time to lower time bound
loop until the current reader time exceeds upper time bound, reading the next frame from the reader and writing it immediately to the writer
close the reader
end of for loop
close the writer
  1 Comment
Chris Foster
Chris Foster on 2 Jun 2015
I did end up using seconds instead of frame rate and similar to "image analyst"'s advice I did read and write one frame at a time.

Sign in to comment.


Chris Foster
Chris Foster on 2 Jun 2015
Thanks to image analyst and Walter Roberson for the help. Below is what I ended up with. It works great but all the videos have to have the same frame size. I'll try to fix that and update this post if I figure it out.
if true
% code
end
files = dir( 'C:/Users/psychuser/Desktop/Pictures and Misc/ChristiPhone/*.mov' ); %finds all movies in a certain folder
n = 1; %frame counter
y = 2; %!!!!!!!number of seconds you want to choose!!!!!!!!!
newfilename = 'C:/Users/psychuser/Desktop/Pictures and Misc/ChristiPhone/TwoSecond/twoseconds';
writeobj = VideoWriter( newfilename , 'MPEG-4' );
open( writeobj );
for i= 1 : size( files , 1 );
originalfilename = sprintf( 'C:/Users/psychuser/Desktop/Pictures and Misc/ChristiPhone/%s' , files(i,1).name );%gets name of file
obj = VideoReader( originalfilename ); %turns video properties into variable 'obj'
x = obj.Duration; %from that properties file get the duration of video
if x > y %if total number of seconds is greater than desired length of clip
z = fix( x - y ); %z is the last possible second to start reading the video such that the desired length of clip is never longer than actual video
if z == 0 %because we are removing the decimals, z could be 0, which randi won't be able to use
starttime = 0;
else
starttime = randi ( z ); %choose random start point between first frame and last possible frame
end
endtime = ( starttime + y ); %random starttime plus desired clip length
obj.CurrentTime = starttime; %set the start time of the to be copied clip
while obj.CurrentTime <= endtime %read a frame and write a frame to new video until desired length is attained
vidHeight = obj.Height;
vidWidth = obj.Width;
video1 = struct('cdata',zeros(vidHeight,vidWidth,3,'uint8'),'colormap',[]); %fill in all the properties
video1(n).cdata = readFrame(obj);
writeVideo( writeobj , video1(n).cdata );
n = n + 1;
clear video1
end
else % if to-be-copied video is less than two seconds then choose the whole video
while hasFrame(obj)
vidHeight = obj.Height;
vidWidth = obj.Width;
video1 = struct('cdata',zeros(vidHeight,vidWidth,3,'uint8'),'colormap',[]);
video1(n).cdata = readFrame(obj);
writeVideo( writeobj , video1(n).cdata );
n = n + 1;
clear video1
end
end
clear x z originalfilename obj video choicerng starttime endtime vidHeight vidWidth %clear variables
end
close( writeobj ); %finalize video
clear all
  1 Comment
Walter Roberson
Walter Roberson on 2 Jun 2015
I recommend you learn to use fullfile() instead of sprintf()'ing your filenames.
Your line
starttime = randi ( z );
only starts on integer second boundaries. To be more flexible, you could use
starttime = rand() * z;
which will start between 0 and z.
Your reading and writing of frames is not efficient. Change to
video1data = readFrame(obj);
writeVideo( writeobj , video1data );
with no need for "n" at all. You can compress it all down to
writeVideo(writeobj, readFrame(obj));

Sign in to comment.

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!