iosobjective-cmetadataxmp360-degrees

iOS - Read/Write XMP metadatas of mp4 videos


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.


Solution

  • I've finally succeeded, using the c++ port of "spatial-media" instead of Adobe's xmp toolkit.

    spatial-media (github repository)