Created
January 27, 2023 07:51
-
-
Save palon7/6ae5405ceaec6b4beeb9f9928af0d7f6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* MusicXMLから歌詞を読み込んでマーカーとして配置するスクリプト | |
* | |
* Copyright (c) 2023 Ryota Uno <palon@palon.org> | |
* | |
* Released under the MIT license. | |
* see https://opensource.org/licenses/MIT | |
* | |
* CeVIO AIから書き出したMusicXMLでのみ動作を確認しました。 | |
* AfterEffects 2023で動作確認済み | |
* | |
*/ | |
function getDivision(attributes) { | |
var divisions = 0; | |
for (var i = 0; i < attributes.length(); ++i) { | |
if (attributes[i].divisions.length() > 0) { | |
divisions = parseInt(attributes[i].divisions[0]); | |
} | |
} | |
if (divisions <= 0) { | |
return null; | |
} | |
return divisions; | |
} | |
/** | |
* @param {string} contents | |
*/ | |
function parseXML(contents) { | |
var xml = new XML(contents); | |
var partList = xml.descendants("part"); | |
var part = partList[0]; | |
if (partList.length() < 1) { | |
alert("Can't parse MusicXML: No part in file"); | |
return; | |
} else if (partList.length() > 1) { | |
alert("More than 1 part detected. Using first part."); | |
} | |
var measures = part.measure; | |
if (measures.length() < 1) { | |
alert("No measure found"); | |
return; | |
} | |
var attributes = measures[0].attributes; | |
var division = getDivision(attributes); | |
if (!division) { | |
alert("Invalid division"); | |
return; | |
} | |
$.writeln("Division:" + division); | |
var bpm = parseInt(measures[0].sound[0].attributes("tempo")[0]); | |
if (!bpm || bpm < 1) { | |
alert("Failed to get tempo"); | |
return; | |
} | |
$.writeln("BPM:" + bpm); | |
// parsing note | |
var time = 0; | |
var lyricsCount = 0; | |
var lyricsList = {}; | |
for (var i = 0; i < measures.length(); ++i) { | |
var notes = measures[i].note; | |
for (var j = 0; j < notes.length(); ++j) { | |
var note = notes[j]; | |
if (note.duration.length() < 1) continue; // skip if no duation | |
if (note.lyric.length() > 0) { | |
lyricsList[time.toString()] = note.lyric.text[0]; | |
lyricsCount++; | |
} | |
time = time + parseInt(note.duration[0]); | |
} | |
} | |
if (lyricsCount < 1) { | |
alert("No lyrics in file"); | |
return; | |
} | |
app.beginUndoGroup("Add lyrics"); | |
app.activeViewer.setActive(); | |
var activeComp = app.project.activeItem; | |
if (!activeComp || !(activeComp instanceof CompItem)) { | |
alert("Please open composition"); | |
return; | |
} | |
var layer; | |
var newLayer = true; | |
if ( | |
activeComp.selectedLayers.length > 0 && | |
activeComp.selectedLayers[0] instanceof AVLayer | |
) { | |
var cnf = confirm("Add marker to current layer? (or add new null layer)"); | |
if (cnf == true) { | |
layer = activeComp.selectedLayers[0]; | |
newLayer = false; | |
} | |
} | |
if (newLayer) { | |
layer = activeComp.layers.addNull(activeComp.duration); | |
} | |
layer.source.name = "Lyrics"; | |
for (var note in lyricsList) { | |
var timeDuration = parseInt(note); | |
var timeSec = (timeDuration / division) * (60 / bpm); | |
var lyric = lyricsList[note]; | |
$.writeln(timeSec + ":" + lyric); | |
var myMarker = new MarkerValue(lyric); | |
layer.marker.setValueAtTime(timeSec, myMarker); | |
} | |
app.endUndoGroup(); | |
} | |
function main() { | |
var xmlFile = File.openDialog( | |
"Choose source musicxml", | |
"MusicXML:*.musicxml;*.xml" | |
); | |
if (!xmlFile) return; | |
xmlFile.encoding = "UTF8"; | |
if (!xmlFile.exists || !xmlFile.open("r")) { | |
throw new Error("Could not open file."); | |
} | |
var content = xmlFile.read(); | |
xmlFile.close(); | |
parseXML(content); | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment