Tuesday, December 21, 2010

Picasa MXF Analysis Script

Lately I’ve been working on a PowerShell script to fix the chronological order of files within a Picasa FaceMovie project file (MXF). It has been quite an ordeal, but I’m getting a lot of helpful feedback from the community (thanks you guys!).

We still have a problem with some files and that’s why I wrote up an analysis script to analyze all the files in the MXF file and create a report (.liz) from it. So, if you have problems with the script, please send me the .mxf, .mxf.backup and the .liz file and I’ll check if I can fix the problem.

Without further ado, here’s the script:

#set variables
$file =  "c:\Users\{UserName}\Pictures\Picasa\Google Movies\{FileName}.mxf";
$picturePath = "C:\Users\{UserName}\Pictures";
$desktopPath = "C:\Users\{UserName}\Desktop";
$documentPath = "C:\Users\{UserName}\Documents";

#set encoding for our non-ASCII brothers
$OutputEncoding = [System.Text.Encoding]::Unicode

#clears the screen
cls; 

function Seek
{
    param([System.IO.FileStream] $fs, [string] $searchString)
    
    $search = $searchString.ToCharArray()
    
    $result = -1
    $position = 0
    $stored = -1 
    $begin = $fs.Position 
    [int] $c = 0

    #read byte by byte
    while ($true)
    {
        $c = $fs.ReadByte() 
        
        if($c -eq -1)
        {
            break;
        }
    
        #check if data in array matches
        if ( $c -eq $search[ $position ])
        {
            #if charater matches first character of 
            #seek string, store it for later
            if ($stored -eq -1 -and $position -gt 0 -and $c -eq $search[ 0 ])
            {
                $stored = $fs.Position;
            }

            #check if we're done
            if ($position + 1 -eq $search.Length)
            {
                #correct position for array lenth
                $result = $fs.Position - $search.Length;
                #set position in stream
                $fs.Position = $result;
                break;
            }

            #advance position in the array
            $position++;
        }
        #no match, check if we have a stored position
        elseif ($stored -gt -1)
        {
            #go to stored position + 1
            $fs.Position = $stored + 1;
            $position = 1;
            $stored = -1; #reset stored position!
        }
        #no match, no stored position, reset array
        #position and continue reading
        else
        {
            $position = 0;
        }
    }

    #reset stream position if no match has been found
    if ($result -eq -1)
    {
        $fs.Position = $begin;
    }
    
    return $result;
}

function GetXmpDate
{
    param([string] $file)

    $result = ""      
    $o = New-Object System.IO.FileInfo $file 
    $reader = $o.OpenRead()
  
    $i = Seek $reader 'xap:CreateDate="'
    
    if($i -gt -1)
    {
        $reader.Position = $reader.Position + 16

        for ($a=0; $a -le 18; $a++)
        {
            $result = $result + [System.Convert]::ToChar($reader.ReadByte())
        }
        
        $result = $result.Replace("-", ":");
        $result = $result.Replace("T", " ");        
    }
    
    $reader.close()
    $reader.dispose()
    return $result;
        
}

function GetExifDate
{
    param([string] $picture)
    
    [string] $date = "";
    
    #continue on errors
    trap [Exception] { continue; }
        
    #create a .net bitmap
    $img = New-Object -TypeName system.drawing.bitmap -ArgumentList $picture;
        
    #construct date
    $date = "";
    foreach($i in $img.GetPropertyItem(36867).value[0..18]) 
    { 
        $date += [Char]$i; 
    }
    
    $img.Dispose();
    
    return $date;
}

function GetLastModificationDate
{
    param([string] $file)

    $o = New-Object System.IO.FileInfo $file 
    return $o.LastWriteTime.ToString("yyyy':'MM':'dd' 'HH':'mm':'ss")
}

function GetDate
{
    param([string] $file)
 
    $date = GetExifDate $file
  
    if($date -eq "" -or $date[0] -eq '0')
    {
        $date = GetXmpDate $file 
        
        if($date -eq "" -or $date[0] -eq '0')
        {
            $date = GetLastModificationDate $file
        }   
    }    
    return $date
}

Write-Host "Starting conversion... (might take a loooohoooong while)";
Write-Host "";

$s = New-Object -TypeName 'System.Text.StringBuilder'

#load the file
$r = New-Object -TypeName 'System.Xml.XmlTextReader' -ArgumentList $file
$source =  New-Object 'System.Xml.XmlDocument'
$source.Load($r)

#extra counter for if two foto's were taken on the same date/time
#it'll prevent collisions on the sorted list
$c = 0;

#query all trans-nodes with a file-name
foreach($node in $source.SelectNodes("/CTransTimeline/trans[src/filename != '']"))
{
    $c++
    Write-Host '.' -NoNewLine
    
    if($c % 70 -eq 0) { Write-Host '' }

    #prepare picture path
    $picture = $node.src.filename;
    
    $s.AppendLine($c) | Out-Null
    $s.AppendLine($picture) | Out-Null
    
    if($picture.StartsWith("$" + "0")) 
    { 
        $picture = $picturePath + $picture.SubString("2"); 
    }    
    elseif($picture.StartsWith("$" + "1")) 
    {
        $picture = $desktopPath + $picture.SubString("2"); 
    }
    elseif($picture.StartsWith("$" + "4")) 
    {
        $picture = $documentPath + $picture.SubString("2"); 
    }
    
    $s.AppendLine($picture) | Out-Null
      
    if(Test-Path $picture)
    {          
        [string] $date = GetDate $picture   
        $s.AppendLine($date) | Out-Null
    }
    else
    {
        $s.AppendLine('Not found') | Out-Null
    }
}

$r.close()  | Out-Null

Write-Host ''
Write-Host $s.ToString()

$file = $file + '.liz' 
$s.ToString() | Out-File $file

Write-Host "Finished... saved resuts to " + $file;

Added document path as $4

4 comments:

  1. Just updated the part of "$0" to "$" + "0" and $1 to "$" + "1".

    ReplyDelete
  2. Added "$4" which is the document path.

    ReplyDelete
  3. Hello,

    I hope that you will still answer me even if this script is old now, I've been trying to do the same thing for AVID MXF, but as it looks your script doenst work for avid mxf, do you have any idea how make it work?

    ReplyDelete
    Replies
    1. This script is only for Picasa. It analyzes paths within the FaceMovie. I'm not sure if AVID uses the same technique (xml file with paths).

      Delete

Please feel free to leave a comment. When you are using the option to react anonymous, please add your name to the comment ;-).