0

Adding to image image info (aka metadata)

Hey, this is my first time dealing with rv's Python API, so please excuse the n00b-like nature of this question.

I have a Python dictionary of key/value pairs that I would like to add to my image metadata. Not bake it out, mind you: on file load, this dict (supplied in-line) would be parsed and each key/value pair would be added to the image info.

For example, I have a dict like:

     metadata = { 'shutterAngle': 180.0, 'focalLength': 35.0, 'name':'cam_anim' }

When transposed, I want the user to be able to examine image info (or hit the 'i' hotkey) and see:

shutterAngle: 180.0
focalLength: 35.0
name: cam_anim

I can't seem to locate the correct command to do this. I know commands.sourceAttributes will return image info, but how to I set it?

Thanks,
AG

18 comments

  • 0
    Avatar
    Jon Morley

    Hi Adrian,

    There is an under-documented feature of the RVFileSource node that allows exactly this kind of use. In the simplest from you can do the following:

            commands.newProperty("#RVFileSource.attributes.test", commands.StringType, 1)
            commands.setStringProperty("#RVFileSource.attributes.test", ["This is a test"], True)

    Note: The #<Node> notation chooses the currently viewed/closest node of the specified <Node> type.

    For your purpose you would probably want a loop like the following:

        for key,value in metadata.items():

            commands.newProperty("#RVFileSource.attributes." + key, commands.StringType, 1)
            commands.setStringProperty("#RVFileSource.attributes." + key, [value], True)

    Please give that a shot.

    Thanks,

    Jon

  • 0
    Avatar
    Jon Morley

    Also note that in the next release we will update the online documentation to reflect this feature in the reference manual.

    http://tweaksoftware.com/static/documentation/rv/current/html/rv_reference.html#toc-Section*-9

  • 0
    Avatar
    Adrian Graham

    Awesome! This is exactly what I was looking for.

    I would also like to delete a property or two, it appears I cannot just do:

        commands.deleteProperty( "#RVFileSource.attributes.<property>" )

    Is this not what deleteProperty was intended for?

    Thanks,

    AG

  • 0
    Avatar
    Jon Morley

    Hi Adrian,

    That is the correct command. Are you getting an error you can share here?

    It appears to be working for me. Please give me some more information. Can you show me the exact code you are calling?

    Thanks,

    Jon

  • 0
    Avatar
    Adrian Graham

    The code is simply:

        commands.deleteProperty( "#RVFileSource.attributes.Comment" )

    And the error is:

    ERROR: Exception thrown while calling commands.deleteProperty -- exception: "invalid property name #RVFileSource.attributes.Comment", program exception
    Traceback (most recent call last):
    File "/job/HOME/agraham/.rv/Python/jpgMetadata.py", line 54, in getMetadata
    self.setMetadata( node, metadata, media )
    File "/job/HOME/agraham/.rv/Python/jpgMetadata.py", line 106, in setMetadata
    commands.deleteProperty( "#RVFileSource.attributes.Comment" )

    The 'Comment' property is built-into these jpgs when they come in; could it be one cannot delete existing attributes?

    I'm able to blank it with:

        commands.setStringProperty( "#RVFileSource.attributes.Comment", [''], True )

    but I'd rather delete it.

    Thanks!

  • 0
    Avatar
    Jon Morley

    Hi Adrian,

    This mechanism will only allow you to add temporary attributes for display purposes in RV. It does not work as a generic modifier for existing metadata in the image file as in the example you shared with me. That "Comment" field is likely defined in the exif data of the jpeg. I would suggest using something outside of RV to load that image and clear out that value if you want to remove it.

    Otherwise the "blank out" you were doing would be the easiest way to keep from showing the long data in your example.

    Thanks,

    Jon

  • 0
    Avatar
    Adrian Graham

    Yeah, I was afraid of that. No worries, I'll just blank out the field.

    Thanks for the quick turnaround!

    AG

  • 0
    Avatar
    Jon Morley

    I am glad that helps!

  • 0
    Avatar
    Adrian Graham

    Hey Jon, one final thing regarding the installation of this rvpkg.

    We've extended the rv path to include our facility-specific plugins and scripts. We install third-party package mods in this kind of structure:

    /facility/rv/plugins
    /facility/rv/plugins/Mu
    /facility/rv/plugins/Packages
    /facility/rv/plugins/Python

    I placed my .rvpkg in the 'Packages' dir, updated the 'rvload2' file in the 'Mu' dir, and updated the 'rvinstall' file in the 'Packages' dir.

    Questions:

    • why did I have to copy the code out of the actual plugin (ie the .py file) and place it in the Python dir? What's the point of a package if you have to copy components elsewhere?
    • why did I have to update the rvload2 file in the Mu dir? If you put one in the Python dir, it's not read.

    Of course, all this is probably taken care of when you use rvpkg -optin, but we have a more defined facility installation pipeline that doesn't allow me to do that.

  • 0
    Avatar
    Alan Trombla

    Hi Adrian!

    why did I have to copy the code out of the actual plugin (ie the .py file) and place it in the Python dir? What's the point of a package if you have to copy components elsewhere?

    The point of the package is to provide a single file that can be distributed as a unit, etc.   The package can contain essentially anything and those files may be read by anything (nuke, maya, rv, etc) so they need to be available as files, not packed up in a package.  Also of course it would greatly slow startup if RV had to unpack all the packages during startup.  As you say, it does all work pretty seamlessly if you can add and install packages with the utilities we provide instead of rolling your own process.  Understand that that's not possible sometimes ...

    why did I have to update the rvload2 file in the Mu dir? If you put one in the Python dir, it's not read.

    All modes in RV are Mu modes (python modes have corresponding Mu mode objects), and they are managed by a single ModeManager that is written in Mu.  So the list of modes in rvload2 is best understood as state for the ModeManager (which happens to be in the Mu directory).

    Hope that clarifies,

    Alan

  • 0
    Avatar
    Adrian Graham

    Yet another question...

    Now my plugin is stepping on another custom plugin that uses the 'after-progressive-loading' event. Blake told me that you can put the 'order' of load as a 4th argument here:

    self.init( 'xml-to-metadata-mode',
        None,
        [('frame-changed', self.getMetadata, 'Convert XML metadata')],
        None,
        'zz'  # <-- here, I think?
    )

    I've tried changing that arg to 'aa' and 'zzz' (Blake's is 'zz'), to no avail. Setting it to 'aa' steps on his plugin and 'zzz' prevents my plugin from loading at all.

    To be honest, that's the wrong event, as I need this plugin to run for every frame; the metadata may be changing frame to frame. I tried the 'frame-changed' event, however it runs for every frame, regardless of whether the frame is cached or not. This could cause performance issues, having to run all the time for every frame.

    So I guess I have two questions:

    1. Can you have multiple plugins call the same event, yet both run successfully?
    2. Can I call a plugin using the 'frame-changed' event, but only run once per frame, when the frame is cached to memory?

    Thanks!
    AG 

  • 0
    Avatar
    Jon Morley

    Hi Adrian,

    To answer your questions:

    1) Can you have multiple plugins call the same event, yet both run successfully?

    Yes. You just have to make sure to reject the event when your plugin is done with it, if you want other packages to have a chance to see it. simply add event.reject() to your method if the incoming event is named event.

     

    2) Can I call a plugin using the 'frame-changed' event, but only run once per frame, when the frame is cached to memory?

    Yes. You can bind to frame-changed and store your own internal value in your package for the last frame you processed. If the frame number has not changed when your method is called again, then you can stop processing. Will that work for you?

     

    Thanks,

    Jon

  • 0
    Avatar
    Jon Morley

    Hi Adrian,

    I think I am still missing what is going on here. At least the part that has you stuck. It sounds like you want to make sure you do some sort of metadata lookup/manipulation once for every frame in a source. Is that correct? Can you take a step back and give me a high level description of what you are shooting for? I am sorry I have not quite picked up the plot yet.

    From what I have understand so far it seems like keeping an internal list of visited frames is exactly the approach I would use. Please tell me more.

    Thanks,

    Jon

  • 0
    Avatar
    Adrian Graham

    Thanks, Jon.

    In regards to answer 1, got it. That seems to work.

    As for answer 2, a bit of a tangent:

    What I now realize is that I'm not examining each frame; the only thing I'm looking at is the media itself:

    for node in commands.nodesOfType('RVFileSource'):
         media = commands.getStringProperty(node+'.media.movie', 0, 1)[0]

    Therefore:

        media=/path/to/images/image.1001-1011#.jpg

    Is because I ran rv with that sequence as an argument. So, I'm currently not operating on the current frame's metadata, but the entire sequence's.

    I'm actually unsure how to query the current frame to retrieve unique metadata values. I've tried all the usual suspects, but haven't found where the individual frame name is stored.

    I've found how to bind to events in the __init__, but do I need to? I can just store a list of frames for which metadata has been created and not examine frames that are already in this list. But I'll need to query the file the current frameis pointing at for that.

    Thanks.

  • 0
    Avatar
    Adrian Graham

    Sorry about the delayed response, thanks for keeping this thread going.

    The ultimate goal of this plugin is to display the metadata embedded in jpg files in RV. Data such as camera info (focalLength, shutterAngle, etc) and scene info (scene file name, asset version) has been embedded into these jpgs as part of our playblasting workflow.

    We have a metadata Python API to extract the data from these jpgs. The big question here is how to apply this in an efficient manner, without affecting playback performance.

    The order of operations I'm currently using is:

    1. On plugin init, loop over each file in the sequence, query its metadata and save to a global dictionary
    2. Apply each frames' metadata (bound to the 'frame-changed' event) using the code you helped me figure out earlier on:
          for key,value in metadata.items():
              commands.newProperty("#RVFileSource.attributes." + key, commands.StringType, 1)
              commands.setStringProperty("#RVFileSource.attributes." + key, [value], True)

    Here are some issues I'm still running into:

    • During initialization, I loop over each frame in the sequence. To do this, I'm using:

      for node in commands.nodesOfType('RVFileSource'):
          input_paths = commands.getStringProperty( node+'.media.movie', 0, 1 ) 
          for path in input_paths:
              files = commands.existingFilesInSequence( path )

      So I have a list of files in a sequence, which I iterate over, extracting metadata and saving to a global dictionary. As this happens on initialization, it shouldn't affect playback performance.

      Later on in the code, when I actually set the file info (using the newProperty/setStringProperty code above), I have to query the current frame and match it to an entry in the global dictionary.

      I can't find a way to query either the filename or the actual frame number RV is looking at. commands.frame() and frameStart() both return an absolute frame number (ie starting at 1).

      If my sequence starts at 1001, I get a value of 1 returned from both commands. I've resorted to parsing the 0th entry in the list of files, looking for the frame number and using that as my offset. I assume something's broken in the API with commands.frameStart?
       
    • The other issue I'm running into is a performance issue. It appears I have to set this image info every frame, whether or not the frame is cached to memory. I have a global dict that I can look up each frames' metadata, but I still have to do a number of things to get the actual file the frame is pointing at (such as query the current frame, apply the offset, and build the filename from the existing input_path var). I suppose I could just make a list of dictionaries, with its list index equal to the absolute frame number. I'm concerned things would get confusing if I had a fractured sequence.

      Once that's done I have to set the image info every frame, then blank out the 'Comment' property. If I only operate on each frame once, the image info freezes on the last frame processed; it's not saved into each frames' cache. If I set the metadata every frame no matter what, I see a performance hit where HD jpgs won't play at 24fps even after they're cached.

      Am I using the wrong event? Is there a way to cache this image info along with each frame?

    Thanks for reading down this far, if this doesn't make sense, please let me know.

    AG

  • 0
    Avatar
    Jon Morley

    Hi Adrian,

    I think this approach has reached its limit of applicability. Setting properties in the attributes component of the file source is really designed to be used per source, and not per frame. Furthermore, changing it every frame causes RV to reevaluate the image associated with the file source because the attributes live on the image buffer for that frame. Thus the cache is invalidated and the playback performance takes a hit.

    I think the way you will want to solve this is through a custom interface package of your own. There are several benefits to the approach.

    1. You don't impact playback performance by thrashing the cache each frame.

    2. You don't have to display the unwanted Comment field at all.

    3. It is easier to focus on the data you want highlighted on each frame.

    I am attaching a sample that shows one approach to creating your own info-widget-like mode. It is a great complement to the code you have already written for reading metadata. I would modify this example Mu package to use calls to your EXIF/Python code instead of using the session file like it currently does. Here is the documentation for that:

     2.2: Calling Python From Mu

    You can keep the entire dictionary in Python where the keys are the filenames and the values are the dictionaries of key/value metadata pairs you want to display for each frame. Then in the render method of the Mu package you can ask your Python what metadata to display for the filename being rendered.

    Please take a look at the attached code and let me know if/where you get stuck.

    Thanks,

    Jon

  • 0
    Avatar
    Jon Morley

    Attaching session and package referenced here:

  • 0
    Avatar
    Nicholas Lim

    Hi Jon, I'm highly interested in a sample that's in a form of zipped python rvpkg as well. I'm trying to implement the same just like how shotgun now integrates with RV but instead we are using Tactic and I can query data from the tactic server via the Python API.

    Any quickstart sample would be greatly appreciated!

     

    Thanks,

    -Nic

Please sign in to leave a comment.