Connexion rapide:  

Forum: VirtualDJ Technical Support

Sujet Replace BPM from Scan with BPM from Tag / Python Script for manipulating xml
Hi VDJ Experts,
I have a collection of songs where I have BPM that I know to be correct. These are stored in the bpm tag of the individual songs. When I import the songs to VDJ it screws up the BPM, however, as the songs are from 1930es and this Jazz music is not easy to scan well because among other things the temp range is 60-300 bpm. I know I can turn off that my correct bpm in the tag are overwritten, but I would like to actually keep the SetTagsAuto feature on as I like it for other tags. How can I still get the bpm from the tag to be displayed in VDJ?

Is any on the following possible or is there another way to get my previously gathered bpm displayin in VDJ?

a) restricting the tempo range for VDJ's bpm analyser such that at least most songs are correct?
b) displaying the tagged bpm rather the scanned bpm in VDJ?
c) batch replacing the scanned bpm with the tagged bpm?
d) same as c) but with a Python script. I actually already did this and the xml file really has onyl 1 bpm scanned value modified (replaced from tagged bpm value). This WORKED when I manually did the replacement. But when I let the Python script do it, VDJ said the database is corrupted and deleted the scanned bpm...

The Python script I wrote essentially
- imports the database.xml,
- does the single 1 bpm replacement (by design in my current database only 1 song has different scanned and tagged bpm)
- converts the xml to a string
- cleans the string because vdj needs &apos for '
- writes the string to an xml again
- and then replaces the VDJ database.xml with the new modified xml

Here is the Python script:


import os
from lxml import etree

# escape '
table = str.maketrans({
"'": "'", # only thing not handled by tree.write() !!!!
})
def escape_apostrophe(txt):
return txt.translate(table)

# Root
folder = "/Users/mo/Documents/VirtualDJ"
vdj_db = "database.xml"
xml_path = folder + "/" + vdj_db
tree = etree.parse(xml_path)
root = tree.getroot()
# we only need to escape apostrophes in the following name elements (and the comments)
name_elements = ["FilePath", "Author", "Title", "Album", "Genre", "Composer"]

# Iterate over each "Song" element
for song in root.iter("Song"):
filepath = str(song.get("FilePath"))
#print(filepath)
# Find the value of the "Bpm" tag from the "Tags" element within the current "Song"
org_bpm = None
tags_element = song.find("Tags")
if tags_element is not None:
org_bpm = tags_element.get("Bpm")
if org_bpm is not None:
bpm = org_bpm

# Find the "Scan" element within the current "Song" and set its "Bpm" attribute
scan_element = song.find("Scan")
if scan_element is not None and bpm is not None:
scan_element.set("Bpm", bpm)

# Iterate over each element in the tree
for element in root.iter():
# Escape the text content of the Comment element
if "Comment" in str(element):
if element.text:
#print("Comment")
element.text = escape_apostrophe(element.text)

# Escape the attribute values of the other element
element_items = dict(element.items())
for attr_name in element_items.keys():
if attr_name in name_elements:
attr_value = element_items.get(attr_name)
element.set(attr_name, escape_apostrophe(attr_value))

# get string, clean it, output it
xml_string = etree.tostring(root, encoding="unicode")
xml_string = xml_string.replace(''', ''')
xml_string = xml_string.replace('/>', ' />')
xml_string += '\n' # Append an empty new line at the end
xml_string = '<?xml version="1.0" encoding="UTF-8"?>\n' + xml_string
print(xml_string.split('\n')[:2])

file_path = os.path.join(folder, vdj_db)
with open(file_path, 'w', encoding="UTF-8") as file:
file.write(xml_string)
 

Posté Thu 06 Jun 24 @ 6:34 pm
It sounds like your python script is somehow mangling the expected format for database.xml.

It might be helpful to back up your database.xml file and create a new database with just one song. Then run the script on the one-song-db and post the results here so we can see what's wrong.
 

Posté Thu 06 Jun 24 @ 9:46 pm
szemekPRO InfinityMember since 2019
user21007643 wrote :

Here is the Python script:


I changed a few things in your script. I tested it and VirtualDJ doesn't return a warning about a corrupted database.


import os
from lxml import etree

# escape '
table = str.maketrans({
"'": "&apos;", # only thing not handled by tree.write() !!!!
})
def escape_apostrophe(txt):
return txt.translate(table)

# Root
folder = "/Users/przemek/Documents/VirtualDJ"
vdj_db = "database.xml"
xml_path = folder + "/" + vdj_db
tree = etree.parse(xml_path)
root = tree.getroot()
# we only need to escape apostrophes in the following name elements (and the comments)
name_elements = ["FilePath", "Author", "Title", "Album", "Genre", "Composer", "Remix"]

# Iterate over each "Song" element
for song in root.iter("Song"):
filepath = str(song.get("FilePath"))
#print(filepath)
# Find the value of the "Bpm" tag from the "Tags" element within the current "Song"
bpm = None
org_bpm = None
tags_element = song.find("Tags")
if tags_element is not None:
org_bpm = tags_element.get("Bpm")
if org_bpm is not None:
bpm = org_bpm

# Find the "Scan" element within the current "Song" and set its "Bpm" attribute
scan_element = song.find("Scan")
if scan_element is not None and bpm is not None:
scan_element.set("Bpm", bpm)

# Iterate over each element in the tree
for element in root.iter():
# Escape the text content of the Comment element
if "Comment" in str(element):
if element.text:
#print("Comment")
element.text = escape_apostrophe(element.text)

# Escape the attribute values of the other element
element_items = dict(element.items())
for attr_name in element_items.keys():
if attr_name in name_elements:
attr_value = element_items.get(attr_name)
element.set(attr_name, escape_apostrophe(attr_value))

# get string, clean it, output it
xml_string = etree.tostring(root, encoding="unicode")
xml_string = xml_string.replace('&amp;apos;', '&apos;')
xml_string = xml_string.replace('/>', ' />')
xml_string = xml_string.replace('\n', '\r\n')
xml_string += '\r\n' # Append an empty new line at the end
xml_string = '<?xml version="1.0" encoding="UTF-8"?>\r\n' + xml_string
print(xml_string.split('\r\n')[:2])

file_path = os.path.join(folder, vdj_db)
with open(file_path, 'w', encoding="UTF-8") as file:
file.write(xml_string)


1) Added Remix to list
name_elements = ["FilePath", "Author", "Title", "Album", "Genre", "Composer", "Remix"]

2) Set bpm to None, because script failed on my database.xml in line if scan_element is not None and bpm is not None: with undefined bpm
bpm = None

3) Changed line endings to \r\n


xml_string = xml_string.replace('\n', '\r\n')
xml_string += '\r\n' # Append an empty new line at the end
xml_string = '<?xml version="1.0" encoding="UTF-8"?>\r\n' + xml_string
print(xml_string.split('\r\n')[:2])
 

Posté Sat 08 Jun 24 @ 7:49 am
szemekPRO InfinityMember since 2019
Sidenote: for quirks in database.xml format it might be useful to use raw strings

database = open(xml_path, "r", newline="").read()
lines = database.splitlines(keepends=True)
 

Posté Sat 08 Jun 24 @ 8:01 am
@Szemek: OMG your edits made it work!!! Thank you so much 🤘
 

Posté Mon 10 Jun 24 @ 12:52 pm