I need to read and inject XMP metadatas in an mp4 container.
I know this is possible on android with the "mp4parser" library, but I couldn't find an equivalent for iOS.
For the read part, is it possible to read every footage from the camera roll to inspect their 360 XMP metadatas quickly ?
For the writing, I'm trying to use Adobe's XMP toolkit. I have an mp4 video in a folder, and I want to inject into it some 360 metadatas.
After injecting the metadatas (I suppose it works), I export the video to the camera roll, but it looks like the video is converted to m4v and it lost every metadata I've written. Is it expected, or is my code wrong ?
Here's the code :
MetadataManager.mm
#import "MetadataManager.h"
#define IOS_ENV 1
#include <string>
#define TXMP_STRING_TYPE std::string
#define XMP_INCLUDE_XMPFILES 1
#include "XMP.incl_cpp"
#include "XMP.hpp"
#include <iostream>
#include <fstream>
using namespace std;
#implementation MetadataManager {
}
+ (void)write360Metadatas:(NSString *)filePath {
if (!SXMPMeta::Initialize())
exit(1);
if (!SXMPFiles::Initialize())
exit(1);
SXMPFiles myFile;
XMP_OptionBits opts = kXMPFiles_OpenForUpdate | kXMPFiles_OpenUseSmartHandler;
std::string status = "";
std::string filePathStd = std::string([filePath UTF8String]);
// First, try to open the file
bool ok = myFile.OpenFile(filePathStd, kXMP_UnknownFile, opts);
if( ! ok ){
status += "No smart handler available for " + filePathStd + "\n";
status += "Trying packet scanning.\n";
// Now try using packet scanning
opts = kXMPFiles_OpenForUpdate | kXMPFiles_OpenUsePacketScanning;
ok = myFile.OpenFile(filePathStd, kXMP_UnknownFile, opts);
}
if(ok){
SXMPMeta meta;
myFile.GetXMP( &meta );
displayPropertyValues(&meta);
injectMetadatas(&meta);
// Check we can put the XMP packet back into the file
if(myFile.CanPutXMP(meta))
{
// If so then update the file with the modified XMP
myFile.PutXMP(meta);
}
// Close the SXMPFile. This *must* be called. The XMP is not
// actually written and the disk file is not closed until this call is made.
myFile.CloseFile();
}
}
SXMPMeta createXMPFromRDF()
{
const char * rdf =
"<rdf:SphericalVideo xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'"
" xmlns:GSpherical='http://ns.google.com/videos/1.0/spherical/'>"
"<GSpherical:Spherical>true</GSpherical:Spherical>"
"<GSpherical:Stitched>true</GSpherical:Stitched>"
"<GSpherical:StitchingSoftware>Spherical Metadata Tool</GSpherical:StitchingSoftware>"
"<GSpherical:ProjectionType>equirectangular</GSpherical:ProjectionType>"
"</rdf:SphericalVideo>";
SXMPMeta meta;
// Loop over the rdf string and create the XMP object
// 10 characters at a time
int i;
for (i = 0; i < (long)strlen(rdf) - 10; i += 10 )
{
meta.ParseFromBuffer ( &rdf[i], 10, kXMP_ParseMoreBuffers );
}
// The last call has no kXMP_ParseMoreBuffers options, signifying
// this is the last input buffer
meta.ParseFromBuffer ( &rdf[i], (XMP_StringLen) strlen(rdf) - i );
return meta;
}
void injectMetadatas(SXMPMeta * meta)
{
// Add an item onto the dc:creator array
// Note the options used, kXMP_PropArrayIsOrdered, if the array does not exist it will be created
meta->AppendArrayItem(kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered, "Author Name", 0);
meta->AppendArrayItem(kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered, "Another Author Name", 0);
// Now update alt-text properties
meta->SetLocalizedText(kXMP_NS_DC, "title", "en", "en-US", "An English title");
meta->SetLocalizedText(kXMP_NS_DC, "title", "fr", "fr-FR", "Un titre Francais");
// Display the properties again to show changes
cout << "After update:" << endl;
displayPropertyValues(meta);
// Create a new XMP object from an RDF string
SXMPMeta rdfMeta = createXMPFromRDF();
// Append the newly created properties onto the original XMP object
// This will:
// a) Add ANY new TOP LEVEL properties in the source (rdfMeta) to the destination (meta)
// b) Replace any top level properties in the source with the matching properties from the destination
SXMPUtils::ApplyTemplate(meta, rdfMeta, kXMPTemplate_AddNewProperties | kXMPTemplate_ReplaceExistingProperties | kXMPTemplate_IncludeInternalProperties);
// Display the properties again to show changes
cout << "After Appending Properties:" << endl;
displayPropertyValues(meta);
}
void displayPropertyValues(SXMPMeta * meta)
{
// Read a simple property
string simpleValue; //Stores the value for the property
meta->GetProperty(kXMP_NS_XMP, "CreatorTool", &simpleValue, 0);
cout << "meta:CreatorTool = " << simpleValue << endl;
// Get the first and second element in the dc:creator array
string elementValue;
meta->GetArrayItem(kXMP_NS_DC, "creator", 1, &elementValue, 0);
if(elementValue != "")
{
cout << "dc:creator[1] = " << elementValue << endl;
meta->GetArrayItem(kXMP_NS_DC, "creator", 2, &elementValue, 0);
cout << "dc:creator[2] = " << elementValue << endl;
}
// Get the the entire dc:subject array
string propValue;
int arrSize = meta->CountArrayItems(kXMP_NS_DC, "subject");
for(int i = 1; i <= arrSize;i++)
{
meta->GetArrayItem(kXMP_NS_DC, "subject", i, &propValue, 0);
cout << "dc:subject[" << i << "] = " << propValue << endl;
}
// Get the dc:title for English and French
string itemValue;
string actualLang;
meta->GetLocalizedText(kXMP_NS_DC, "title", "en", "en-US", 0, &itemValue, 0);
cout << "dc:title in English = " << itemValue << endl;
meta->GetLocalizedText(kXMP_NS_DC, "title", "fr", "fr-FR", 0, &itemValue, 0);
cout << "dc:title in French = " << itemValue << endl;
// Get dc:MetadataDate
XMP_DateTime myDate;
if(meta->GetProperty_Date(kXMP_NS_XMP, "MetadataDate", &myDate, 0))
{
// Convert the date struct into a convenient string and display it
string myDateStr;
SXMPUtils::ConvertFromDate(myDate, &myDateStr);
cout << "meta:MetadataDate = " << myDateStr << endl;
}
cout << "----------------------------------------" << endl;
}
#end
Any help would be appreciated, thanks.
I've finally succeeded, using the c++ port of "spatial-media" instead of Adobe's xmp toolkit.
spatial-media (github repository)
I am making a small project on extracting video frames and remake it into video.
How to make sequence images back to video again?
Here is part of my extracting video frames code.
if (n_frame % 3 == 0)
{
//Save an image
sprintf(filename, "frame%.3d.jpg", n_save++);
imwrite(filename, frame);
cout << "save: " << filename << endl;
}
I named my images frame000, frame001, frame002....etc.
I am using opencv 2.4.11.
Thanks a lot!
you used FFmpegFrameRecorder
String path = Environment.getExternalStorageDirectory().getPath() + "/Video_images";
File folder = new File(path);
File[] listOfFiles = folder.listFiles();
if (listOfFiles.length > 0) {
iplimage = new opencv_core.IplImage[listOfFiles.length];
for (int j = 0; j < listOfFiles.length; j++) {
String files = "";
if (listOfFiles[j].isFile()) {
files = listOfFiles[j].getName();
System.out.println(" j " + j + listOfFiles[j]);
}
String[] tokens = files.split("\\.(?=[^\\.]+$)");
String name = tokens[0];
iplimage[j] = cvLoadImage(Environment.getExternalStorageDirectory().getPath() + "/Video_images/" + name + ".jpg");
}
recorder = new FFmpegFrameRecorder(Constn.SS, 480, 480);
try {
recorder.setVideoCodec(13);
recorder.setFrameRate(0.4d);
recorder.setPixelFormat(0);
recorder.setVideoQuality(1.0d);
recorder.setVideoBitrate(4000);
startTime = System.currentTimeMillis();
recorder.start();
int time = Integer.parseInt(params[0]);
resp = "Slept for " + time + " milliseconds";
for (int i = 0; i < iplimage.length; i++) {
long t = 1000 * (System.currentTimeMillis() - startTime);
if (t < recorder.getTimestamp()) {
t = recorder.getTimestamp() + 1000;
}
recorder.setTimestamp(t);
recorder.record(iplimage[i]);
}
} catch (Exception e) {
e.printStackTrace();
}
You need VideoWriter - http://docs.opencv.org/trunk/dd/d9e/classcv_1_1VideoWriter.html
Once you construct it with desired file type and path, you feed it with Mat objects containing frames using << operator - i.e.
auto frame = cv::imread("somePicture.png");
auto writer = cv::VideoWriter("out.avi", VideoWriter::fourcc('M','J','P','G'), 24, frame.size());
writer << frame;
writer.release();
The code above will read frame from file, feed it into a video file which has 24fps and MJPG format and AVI container and then the release() method will close the writer.
i am trying capture a video from webcam. But i am always getting a 441 byte size file getting created.
Also in console there is error coming
OpenCVCMD[37317:1478193] GetDYLDEntryPointWithImage(/System/Library/Frameworks/AppKit.framework/Versions/Current/AppKit,_NSCreateAppKitServicesMenu) failed.
Code Snippet
void demoVideoMaker() {
//Camera Input
VideoCapture cap(0);
vidoFeed = ∩
namedWindow("VIDEO", WINDOW_AUTOSIZE);
//Determine the size of inputFeed
Size inpFeedSize = Size((int) cap.get(CV_CAP_PROP_FRAME_WIDTH), // Acquire input size
(int) cap.get(CV_CAP_PROP_FRAME_HEIGHT));
cout<<"Input Feed Size: "<<inpFeedSize<<endl;
VideoWriter outputVideo;
char fName[] = "capturedVid.avi";
outputVideo.open(fName, CV_FOURCC('P','I','M','1'), 20, inpFeedSize, true);
if (!outputVideo.isOpened()) {
cout<<"Failed to write Video"<<endl;
}
//Event Loop
Mat frame;
bool recordingOn = false;
while(1){
//Process user input if any
char ch = char(waitKey(10));
if (ch == 'q') {
break;
}if (ch == 'r') {
recordingOn = !recordingOn;
}
//Move to next frame
(*vidoFeed)>>frame;
if (frame.empty()) {
printf("\nEmpty Frame encountered");
}else {
imshow("VIDEO", frame);
if(recordingOn) {
cout<<".";
outputVideo.write(frame);
}
}
}
}
I am using opencv2.4, XCode 8.2 on mac OS Sierra 10.12.1
Tried changing the codec, fps, but nothing helped. I was assuming this would be a straight forward task but got stuck here. Please help.
I am using opencv 2.4.7, windows 7 and vc++2010 to stream mjpeg from a foscam ip camera. cap.isOpened is not null but only the first frame is displayed and breaks from loop in the second round. this is part of the code I am using:
VideoCapture cap("http://IP:PORT/cgi-bin/CGIProxy.fcgi?cmd=snapPicture2&usr=X&pwd=Y&.mjpg"); // open the video file for reading
if ( !cap.isOpened() ) // if not success, exit program
{
std::cout << "Cannot open the video file" << std::endl;
return -1;
}
cap.set(CV_CAP_PROP_POS_MSEC, 30); //start the video at 300ms
double fps = cap.get(CV_CAP_PROP_FPS); //get the frames per seconds of the video
std::cout << "Frame per seconds : " << fps << std::endl;
namedWindow("MyVideo",CV_WINDOW_AUTOSIZE); //create a window called "MyVideo"
int cnt=0;
Mat frame;
for(;;)
{
bool bSuccess = cap.read(frame); // read a new frame from video
if (!bSuccess) //if not success, break loop
{
std::cout << "Cannot read the frame from video file" << std::endl;
break;
}
imshow("MyVideo", frame); //show the frame in "MyVideo" window
if(waitKey(33) == 27) //wait for 'esc' key press for 30 ms. If 'esc' key is pressed, break loop
{
std::cout << "esc key is pressed by user" << std::endl;
break;
}
}
}
I appreciate any help in advance.
look at your url:
"IP:PORT/cgi-bin/CGIProxy.fcgi?cmd=snapPicture2&usr=X&pwd=Y&.mjpg"
snapPicture2 looks suspicious, no ?
i'm pretty sure, there's no problem with your opencv code,
it's more like the url you choose only retrieves 1 frame
please lookup your manual for the correct stream url
http://www.ispyconnect.com/man.aspx?n=foscam
I am reading a avi file, and do some background subtrcation work. The wierd thing is when I use cvRetrieveFrame, I got a strange image, like below:
origin:
cvRetrieveFrame returns:
I don't know what's the problem. Here is my code snippet.
CvCapture* readerAvi = cvCaptureFromAVI( filename.c_str() );
if(readerAvi == NULL)
{
std::cerr << "Could not open AVI file." << std::endl;
return 0;
}
// retrieve information about AVI file
cvQueryFrame(readerAvi); //....get some information, width, height, ....
// grad next frame from input video stream
if(!cvGrabFrame(readerAvi))
{
std::cerr << "Could not grab AVI frame." << std::endl;
return 0;
}
frame_data = cvRetrieveFrame(readerAvi);