#!/usr/bin/python

"""Fix old pre-ardour 2.0 sessions that refer to plugins by name rather than id.

(c) 2007 Paul M. Winkler <stuff@slinkp.com>

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""

import pprint
import os
import sys
import re
import shutil
import time
from lxml import etree

# Start with old CAPS pre-3.0 plugin names that were likely used back then.
old_name_to_id = {
    "CAPS: Eq - 10-band 'analogue' equalizer" : 1773,
    "CAPS: Compress - Mono compressor": 1772,
    "CAPS: Pan - Pan and width": 1788,
    "CAPS: PreampIII - Tube preamp emulation": 1776,
    "CAPS: PreampIV - Tube preamp emulation + tone controls": 1777,
    "CAPS: AmpIII - Tube amp emulation": 1786,
    "CAPS: AmpIV - Tube amp emulation + tone controls": 1794,
    "CAPS: AmpV - Refined tube amp emulation": 2587,
    "CAPS: CabinetI - Loudspeaker cabinet emulation": 1766,
    "CAPS: CabinetII - Refined loudspeaker cabinet emulation": 2581,
    "CAPS: Clip - Hard clipper, 8x oversampled": 1771,
    "CAPS: ChorusI - Mono chorus/flanger": 1767,
    "CAPS: StereoChorusI - Stereo chorus/flanger": 1768,
    "CAPS: ChorusII - Mono chorus/flanger modulated by a fractal": 2583,
    "CAPS: StereoChorusII - Stereo chorus/flanger modulated by a fractal": 2584,
    "CAPS: PhaserI - Mono phaser": 1775,
    "CAPS: PhaserII - Mono phaser modulated by a Lorenz fractal": 2586,
    "CAPS: SweepVFI - Resonant filter, f swept by a Lorenz fractal": 1782,
    "CAPS: SweepVFII - Resonant filter, f and Q swept by a Lorenz fractal": 2582,
    "CAPS: Scape - Stereo delay + Filters": 2588,
    "CAPS: VCOs - Virtual 'analogue' oscillator": 1783,
    "CAPS: VCOd - Double VCO with detune and hard sync options": 1784,
    "CAPS: CEO - Chief Executive Oscillator": 1770,
    "CAPS: Sin - Sine wave generator": 1781,
    "CAPS: White - White noise generator": 1785,
    "CAPS: Lorenz - The sound of a Lorenz attractor": 1774,
    "CAPS: Roessler - The sound of a Roessler attractor": 1780,
    "CAPS: JVRev - Stanford-style reverb from STK": 1778,
    "CAPS: Plate - Versatile plate reverb": 1779,
    "CAPS: Plate2x2 - Versatile plate reverb, stereo inputs": 1795,
    "CAPS: Click - Metronome": 1769,
    "CAPS: Dirac - One-sample impulse generator": 2585,
    "CAPS: HRTF - Head-related transfer function at elevation 0": 1787,
    "10-band eq": 1773,
    "Mono compressor suitable for musical instruments": 1772,
    "Pan and width": 1788,
    "Tube amp emulation": 1786,
    "Tube amp emulation + tone controls": 1794,
    "Tube preamp emulation": 1776,
    "Tube preamp emulation + tone controls": 1777,
    "Amp/speaker emulation": 1766,
    "Hard clipper, 8x oversampled": 1771,
    "Mono chorus/flanger": 1767,
    "Stereo chorus with feedback": 1768,
    "Mono phaser": 1775,
    "2x ladder filter swept by a Lorenz fractal": 1782,
    "Tri / saw / square oscillator": 1783,
    "Double VCO with detune and hard sync": 1784,
    "Chief Executive Oscillator": 1770,
    "Sine generator": 1781,
    "White noise generator": 1785,
    "The sound of a Lorenz attractor": 1774,
    "The sound of a Roessler attractor": 1780,
    "Stanford-style reverb from STK": 1778,
    "Versatile plate reverb": 1779,
    "Versatile plate reverb, 2x2": 1795,
    "Metronome": 1769,
    "IIR-based HRTF at elevation 0": 1787,
}

for name, value in old_name_to_id.items():
    # Apparently caps plugin names used to be lowercase, but I can't find
    # a copy of the old tarball to inspect them from. So, force lowercase.
    old_name_to_id[name.lower()] = value

# Find all currently known plugins.
new_name_to_id = {}
id_to_new_name = {}
plugin_info = os.popen('listplugins').readlines()
plugin_info = [line.strip() for line in plugin_info if not line.endswith('.so:\n')]
info_re = re.compile(r'(.+) \((\d+)/(.+)\)')
for line in plugin_info:
    found = info_re.match(line)
    if not found:
        continue
    name, ladspa_id, label = found.groups()
    ladspa_id = int(ladspa_id)
    id_to_new_name[ladspa_id] = name
    new_name_to_id[name] = ladspa_id
    new_name_to_id[name.lower()] = ladspa_id

name_to_id = old_name_to_id.copy()
name_to_id.update(new_name_to_id)


def fix_tags(tree):
    inserts = tree.xpath('//Insert[@type="ladspa"]')
    for insert in inserts:
        name = insert.get('id')
        print "Name:", name
        if insert.get('unique-id'):
            print "Already have a unique id, skipping"
            continue
        print "Fixing unique id...",
        new_id = name_to_id.get(name.lower())
        if new_id == None:
            print "UH-OH, no new id found, skipping"
            continue
        print "got unique id %s" % new_id,
        insert.set('unique_id', str(new_id))  # Ardour 2.2-ish
        insert.set('unique-id', str(new_id))  # Ardour 2.4
        print "ok"
        if old_name_to_id.has_key(name):
            print "Trying to update the name too...",
            new_name = id_to_new_name.get(new_id)
            if new_name:
                insert.set('id', new_name)
                print "ok, got %r" % new_name
            else:
                print "couldn't find a better one"
    return tree

def get_backup_name(filename):
   now = int(time.time() * 1000)
   return filename + '.backup_before_plugin_fixing.%d' % now

def main():
    infilename = sys.argv[-1]
    if not infilename.endswith('.ardour'):
        sys.stderr.write("""
  Usage: %s foo.ardour
 Argument must be an ardour session file.
"""
 	)
        sys.exit(1)
    print __doc__
    ardourfile = open(infilename, 'r')
    backupname = get_backup_name(infilename)
    print "Making backup copy at %s..." % backupname
    shutil.copyfile(infilename, backupname)
    print "Parsing %s..." % infilename,
    ardourxml = etree.parse(ardourfile)
    print "ok"
    fixed_xml = fix_tags(ardourxml)
    ardourfile.close()
    output = etree.tostring(fixed_xml, xml_declaration=True, encoding="utf-8")
    print "Writing fixed data to %s..." % infilename
    outfile = open(infilename, 'w')
    outfile.write(output)
    print "done!"

if __name__ == '__main__':
    main()
