iOS - Read/Write XMP metadatas of mp4 videos - ios

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)

Related

Write frames to video with opencv on ios

I have a C++ program that can take a sequence of images and create a video and write them to file. This works well on my Mac and Ubuntu computer. I have also compiled my C++ program for ios. When I try and do the same thing on iOS it does not work unfortunately.
I get the following error:
VideoProcessing.cpp:66 INFO| Width: 1920 frame_height: 1080
INFO| Output video path: /private/var/containers/Bundle/Application/0886D03C-55DB-48FA-9085-0AE1A6000B2A/TestVideoProcess.app/output_video0.mov
2022-09-25 13:51:53.535999+0200 TestVideoProcess[72327:4995351] The operation could not be completed
2022-09-25 13:51:53.536 ( 0.681s) [ 4C3917] VideoProcessing.cpp:70 INFO| VideoWriter created
2022-09-25 13:51:53.542 ( 0.688s) [ 4C3917] VideoProcessing.cpp:77 INFO| Writing frame
2022-09-25 13:51:53.542682+0200 TestVideoProcess[72327:4995351] [mMovieWriterInput isReadyForMoreMediaData] Not ready for media data or ...
2022-09-25 13:51:53.542785+0200 TestVideoProcess[72327:4995351] mMovieWriter.status: 3. Error: The operation could not be completed
The code looks like this:
void writeSubVideos(int anchorFrame, const std::string& video_path,const std::string& out_path) {
cv::VideoCapture video_capture;
video_capture.open(video_path);
if (!video_capture.isOpened())
{
LOG_S(ERROR) << "Error opening video file " << video_path;
return;
}
const auto frame_width = video_capture.get(cv::CAP_PROP_FRAME_WIDTH);
const auto frame_height = video_capture.get(cv::CAP_PROP_FRAME_HEIGHT);
LOG_S(INFO) << "Width: " << frame_width << " frame_height: " << frame_height;
LOG_S(INFO) << "Output video path: " << out_path;
auto video_writer = cv::VideoWriter(out_path, cv::CAP_AVFOUNDATION, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), kFrameRate, cv::Size(frame_width, frame_height));
int frame_nbr = 0;
LOG_S(INFO) << "VideoWriter created";
while (1)
{
cv::Mat frame;
video_capture >> frame;
frame_nbr++;
if (frame_nbr >= anchorFrame - kFixedWindow && frame_nbr < anchorFrame + kFrameRate) {
LOG_S(INFO) << "Writing frame";
video_writer.write(frame);
}
if (frame_nbr >= anchorFrame + kFrameRate) {
break;
}
}
}
I have been able to write an image to file and load it again. Is there some sort of permission missing?

Why the interpreter complains that library named "math" does not exist?

Why the interpreter complains that library named "math" does not exist?
As far as I know, this library is loaded when invoking luaL_newstate on Lua-5.3.5.
#include "lua.hpp"
#include <iostream>
#include <assert.h>
#include <fstream>
int main()
{
struct lua_State *L = luaL_newstate();
int ret;
std::string fileName("co.lua");
if(fileName.empty())
{
std::cout << "the filename is empty" << std::endl;
return -1;
}
std::ifstream fileScript(fileName, fileScript.in|std::ios::ate);
if(!fileScript.is_open())
{
std::cout << "open file failed" << std::endl;
return -2;
}
size_t size = fileScript.tellg();
if(size <= 0)
{
std::cout << "file has no valid content" << std::endl;
return -3;
}
std::string textCont(size, '\0');
fileScript.seekg(0);
fileScript.read(&textCont[0], size);
if((ret=luaL_loadbuffer(L, textCont.data(), textCont.length(), "co.lua")) == LUA_OK)
{
if((ret=lua_pcall(L, 0, LUA_MULTRET, 0)) != LUA_OK)
{
std::cout << "error in invoking lua_pcall():" << ret << std::endl;
if(lua_isstring(L, -1))
{
const char *errMsg = lua_tostring(L, -1);
lua_pop(L, 1);
std::cout << "script run encounter err:" << errMsg << std::endl;
}
}
}
}
Here is the code snippet(it's very simple) for the file named "co.lua":
a = 1;
b=2;
a=a+1;
math.sin(a)
Here is the error message in the console:
error in invoking lua_pcall():2
script run encounter err:[string "co.lua"]:29: attempt to index a nil value (global 'math')
The documentation states that you need to call luaL_openlibs or luaL_requiref which does not seem to be the case with your posted program.
To have access to these libraries, the C host program should call the luaL_openlibs function, which opens all standard libraries.
Alternatively (emphasis mine):
Alternatively, the host program can open them individually by using luaL_requiref to call:
luaopen_base (for the basic library)
luaopen_package (for the package library)
luaopen_coroutine (for the coroutine library)
luaopen_string (for the string library)
luaopen_utf8 (for the UTF8 library)
luaopen_table (for the table library)
luaopen_math (for the mathematical library)
luaopen_io (for the I/O library)
luaopen_os (for the operating system library)
luaopen_debug (for the debug library).
These functions are declared in lualib.h.
So change your program's first few lines to something like below.
You also need to compare the return value from luaL_newstate with NULL and handle that error condition.
int main()
{
struct lua_State *L = luaL_newstate();
if( L == NULL ) {
puts( "Lua failed to initialize." );
exit(1);
}
luaL_openlibs( L );
// etc

Google Protocol Buffer C++ on ubuntu

I want to use Google protocol buffer in c++ on Ubuntu in first step I created .proto file
package business;
message Employee
{
required string first_name = 1;
required string last_name = 2;
required string email = 3;
}
message Company
{
required string name = 1;
optional string url = 2;
repeated Employee employee = 3;
}
I can easily translate it to the C++ data access classes by calling:
protoc -I=. --cpp_out=. business.proto
after this step protoc create to file
business.pb.h
business.pb.cc
when I want compile this code I see error
#include <iostream>
#include <fstream>
#include "business.pb.h"
using namespace std;
/// Saves a demo company object to 'company.bin'.
void save()
{
business::Company company;
company.set_name("Example Ltd.");
company.set_url("http://www.example.com");
// 1st employee
{
business::Employee *employee = company.add_employee();
employee->set_first_name("John");
employee->set_last_name("Doe");
employee->set_email("john.doe#example.com");
}
// 2nd employee
{
business::Employee *employee = company.add_employee();
employee->set_first_name("Jane");
employee->set_last_name("Roe");
employee->set_email("jane.roe#example.com");
}
fstream output("company.bin", ios::out | ios::trunc | ios::binary);
company.SerializeToOstream(&output);
}
/// Loads a demo company object from 'company.bin' and dumps its data.
void load()
{
business::Company company;
fstream input("company.bin", ios::in | ios::binary);
company.ParseFromIstream(&input);
cout << "Company: " << company.name() << "\n";
cout << "URL: " << (company.has_url() ? company.url() : "N/A") << "\n";
cout << "\nEmployees: \n\n";
for(int i = 0, n = company.employee_size(); i < n; ++i)
{
const business::Employee &employee = company.employee(i);
cout << "First name: " << employee.first_name() << "\n";
cout << "Last name: " << employee.last_name() << "\n";
cout << "Email: " << employee.email() << "\n";
cout << "\n";
}
}
int main()
{
save();
load();
return 0;
}
for compile I use this command
g++ p1.cpp business.pb.cc `pkg-config --cflags --libs protobuf`
but I see this error
https://i.stack.imgur.com/soQ3Z.png
i solved the problem
1)uninstall old version Google Protocol Buffer
2) instal new version Google Protocol Buffer

OpenCV FileStorage empty after loading

Im trying to load a string into filestorage. I will not have the file to pass filename as a parameter to load it. Instead I recive an xml document as a string. In the doc http://docs.opencv.org/modules/core/doc/xml_yaml_persistence.html#filestorage it is mentioned that source attribute of fs.open can be "text string to read the data from". I run some simple tests with OpenCv CascadeClassifier as a string but I get an empty FileStorage. What am I doing wrong?
CascadeClassifier face_cascade;
std::ifstream t("haarcascade_frontalface_alt.xml");
std::string ClasifierInString((std::istreambuf_iterator<char>(t)),
std::istreambuf_iterator<char>());
cout << ClasifierInString << endl; //I CAN PRINT THE FILE AND SEE IT
cv::FileStorage fs;
if (!fs.open(ClasifierInString, cv::FileStorage::READ | cv::FileStorage::MEMORY | cv::FileStorage::FORMAT_XML))
{
cout << "Couldn't load file into memory" << endl;
return -2;
}
FileNodeIterator it = fs.getFirstTopLevelNode().begin(), it_end = fs.getFirstTopLevelNode().end();
for (; it != it_end; ++it)
{
cout << (string)*it << "\n"; //EMPTY LINE????
}
if (!face_cascade.read(fs.getFirstTopLevelNode()))
{
cout << "Couldn't read file from memory" << endl;
return -1;
}
EDIT:
#sop can't comment yet. Maybe your using older version of OpenCV. I have the file and I am able to load it with:
face_cascade.load("haarcascade_frontalface_alt.xml");
and it works. The problem is I'm unable to read it as a string with face_cascade.read(string)
#berak comment is the correct answer. I used lbpcascade_frontalface.xml as this is new cascade (and is faster! :) Thx for help.
I think your problem is the file name: "haarcascade_frontalface_alt.xml". There is no such a file in the OpenCV flder... Try "haarcascade_frontalface_alt_tree.xml".
Here is my code that works:
cv::CascadeClassifier face_cascade;
std::ifstream t("haarcascade_frontalface_alt_tree.xml");
std::string ClasifierInString((std::istreambuf_iterator<char>(t)), std::istreambuf_iterator<char>());
std::cout << ClasifierInString << std::endl; //I CAN PRINT THE FILE AND SEE IT
cv::FileStorage fs;
if (!fs.open(ClasifierInString, /*cv::FileStorage::READ | */cv::FileStorage::MEMORY | cv::FileStorage::FORMAT_XML))
{
std::cout << "Couldn't load file into memory" << std::endl;
return -2;
}
cv::FileNodeIterator it = fs.getFirstTopLevelNode().begin(), it_end = fs.getFirstTopLevelNode().end();
for (; it != it_end; ++it)
{
std::cout << (std::string)*it << "\n"; //EMPTY LINE????
}
if (!face_cascade.read(fs.getFirstTopLevelNode()))
{
std::cout << "Couldn't read file from memory" << std::endl;
return -1;
}

using cvRetrieveFrame get strange image

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);

Resources