@@ -2,20 +2,34 @@ function plugindef()
22 finaleplugin .RequireDocument = false
33 finaleplugin .RequireSelection = false
44 finaleplugin .NoStore = true
5- finaleplugin .Author = " Robert Patterson"
5+ finaleplugin .Author = " Robert Patterson (folder scanning added by Carl Vine) "
66 finaleplugin .Copyright = " CC0 https://creativecommons.org/publicdomain/zero/1.0/"
7- finaleplugin .Version = " 1.0.1"
8- finaleplugin .Date = " September 25, 2024"
7+ finaleplugin .Version = " 1.0.6"
8+ finaleplugin .Date = " October 2, 2024"
9+ finaleplugin .LoadLuaOSUtils = true
910 finaleplugin .CategoryTags = " Document"
1011 finaleplugin .MinJWLuaVersion = 0.74
12+ finaleplugin .AdditionalMenuOptions = [[
13+ Massage MusicXML Single File...
14+ ]]
15+ finaleplugin .AdditionalUndoText = [[
16+ Massage MusicXML Single File
17+ ]]
18+ finaleplugin .AdditionalDescriptions = [[
19+ Massage a MusicXML file to improve importing to Dorico and MuseScore
20+ ]]
21+ finaleplugin .AdditionalPrefixes = [[
22+ do_single_file = true
23+ ]]
24+ finaleplugin .ScriptGroupName = " Staff Explode"
1125 finaleplugin .Notes = [[
12- This script reads a musicxml file exported from Finale and modifies it to
13- improve the importing into Dorico or MuseScore. The best process is as follows:
26+ This script reads musicxml files exported from Finale and modifies them to
27+ improve importing into Dorico or MuseScore. The best process is as follows:
1428
1529 1. Export your document as uncompressed MusicXML.
1630 2. Run this plugin on the output *.musicxml document.
1731 3. The massaged file name has " massaged" appended to the file name.
18- 3. Import the massaged *.musicxml into Dorico or MuseScore.
32+ 3. Import the massaged *.musicxml file into Dorico or MuseScore.
1933
2034 Here is a list of some of the changes the script makes:
2135
@@ -30,76 +44,51 @@ function plugindef()
3044 \widowctrl\hyphauto
3145 \fs18
3246 {\info{\comment "os":"mac","fs18":"fs24","fs26":"fs32","fs23":"fs29","fs20":"fs26"}}
33- {\pard \sl264 \slmult1 \ql \f0 \sa180 \li0 \fi0 This script reads a musicxml file exported from Finale and modifies it to improve the importing into Dorico or MuseScore. The best process is as follows:\par}
47+ {\pard \sl264 \slmult1 \ql \f0 \sa180 \li0 \fi0 This script reads musicxml files exported from Finale and modifies them to improve importing into Dorico or MuseScore. The best process is as follows:\par}
3448 {\pard \sl264 \slmult1 \ql \f0 \sa0 \li360 \fi-360 1.\tx360\tab Export your document as uncompressed MusicXML.\par}
3549 {\pard \sl264 \slmult1 \ql \f0 \sa0 \li360 \fi-360 2.\tx360\tab Run this plugin on the output *.musicxml document.\par}
3650 {\pard \sl264 \slmult1 \ql \f0 \sa0 \li360 \fi-360 3.\tx360\tab The massaged file name has " massaged" appended to the file name.\par}
37- {\pard \sl264 \slmult1 \ql \f0 \sa0 \li360 \fi-360 4.\tx360\tab Import the massaged *.musicxml into Dorico or MuseScore.\sa180\par}
51+ {\pard \sl264 \slmult1 \ql \f0 \sa0 \li360 \fi-360 4.\tx360\tab Import the massaged *.musicxml file into Dorico or MuseScore.\sa180\par}
3852 {\pard \sl264 \slmult1 \ql \f0 \sa180 \li0 \fi0 Here is a list of some of the changes the script makes:\par}
3953 {\pard \sl264 \slmult1 \ql \f0 \sa0 \li360 \fi-360 \bullet \tx360\tab 8va/8vb and 15ma/15mb symbols are extended to include the last note and extended left to include leading grace notes.\sa180\par}
4054 {\pard \sl264 \slmult1 \ql \f0 \sa180 \li0 \fi0 Due to a limitation in the xml parser, all xml processing instructions are removed. These are metadata that neither Dorico nor MuseScore use, so their removal should not affect importing into those programs.\par}
4155 }
4256 ]]
4357 finaleplugin .HashURL = " https://raw.githubusercontent.com/finale-lua/lua-scripts/master/hash/musicxml_massage_export.hash"
44- return " Massage MusicXML..." , " " , " Massages MusicXML to make it easier to import to Dorico and MuseScore."
58+ return " Massage MusicXML Folder..." ,
59+ " Massage MusicXML Folder" ,
60+ " Massage a folder of MusicXML files to improve importing to Dorico and MuseScore."
4561end
46- local text_extension = " .musicxml"
47- local function remove_processing_instructions (file_path , output_name )
48-
49- local input_file <close> = io.open (file_path , " r" )
62+ do_single_file = do_single_file or false
63+ local xml_extension = " .musicxml"
64+ local add_to_filename = " massaged"
65+ local function alert_error (file_list )
66+ local msg = (# file_list > 1 and " These files do not " or " This file does not " )
67+ .. " appear to be MusicXML exported from Finale:\n\n "
68+ .. table.concat (file_list , " \n " )
69+ finenv .UI ():AlertError (msg , plugindef ())
70+ end
71+ local function remove_processing_instructions (input_name , output_name )
72+ local input_file <close> = io.open (input_name , " r" )
5073 if not input_file then
51- error (" Cannot open file: " .. file_path )
74+ error (" Cannot open file: " .. input_name )
5275 end
53-
5476 local lines = {}
5577 for line in input_file :lines () do
56-
5778 if line :match (" ^%s*<%?xml" ) or not line :match (" ^%s*<%?.*%?>" ) then
5879 table.insert (lines , line )
5980 end
6081 end
61-
6282 input_file :close ()
63-
6483 local output_file <close> = io.open (output_name , " w" )
6584 if not output_file then
66- error (" Cannot open file for writing: " .. file_path )
85+ error (" Cannot open file for writing: " .. output_name )
6786 end
68-
6987 for _ , line in ipairs (lines ) do
7088 output_file :write (line .. " \n " )
7189 end
72-
7390 output_file :close ()
7491end
75- function do_open_dialog (document )
76- local path_name = finale .FCString ()
77- local file_name = finale .FCString ()
78- local file_path = finale .FCString ()
79- if document then
80- document :GetPath (file_path )
81- file_path :SplitToPathAndFile (path_name , file_name )
82- end
83- local full_file_name = file_name .LuaString
84- local extension = finale .FCString (file_name .LuaString )
85- extension :ExtractFileExtension ()
86- if extension .Length > 0 then
87- file_name :TruncateAt (file_name :FindLast (" ." .. extension .LuaString ))
88- end
89- file_name :AppendLuaString (text_extension )
90- local open_dialog = finale .FCFileOpenDialog (finenv .UI ())
91- open_dialog :SetWindowTitle (finale .FCString (" Open MusicXML for " .. full_file_name ))
92- open_dialog :AddFilter (finale .FCString (" *" .. text_extension ), finale .FCString (" MusicXML File" ))
93- open_dialog :SetInitFolder (path_name )
94- open_dialog :SetFileName (file_name )
95- open_dialog :AssureFileExtension (text_extension )
96- if not open_dialog :Execute () then
97- return nil
98- end
99- local selected_file_name = finale .FCString ()
100- open_dialog :GetFileName (selected_file_name )
101- return selected_file_name .LuaString
102- end
10392function fix_octave_shift (xml_measure )
10493 for xml_direction in xmlelements (xml_measure , " direction" ) do
10594 local xml_direction_type = xml_direction :FirstChildElement (" direction-type" )
@@ -153,40 +142,85 @@ function process_xml(score_partwise)
153142 end
154143 end
155144end
156- function append_massaged_to_filename (filepath )
157-
158- local path , filename , extension = filepath :match (" ^(.-)([^\\ /]-)%.([^\\ /%.]+)$" )
159-
145+ function process_one_file (input_file )
146+ local path , filename , extension = input_file :match (" ^(.-)([^\\ /]-)%.([^\\ /%.]+)$" )
160147 if not path or not filename or not extension then
161148 error (" Invalid file path format" )
162149 end
150+ local output_file = path .. filename .. add_to_filename .. xml_extension
151+ remove_processing_instructions (input_file , output_file )
152+ local musicxml = tinyxml2 .XMLDocument ()
153+ local result = musicxml :LoadFile (output_file )
154+ if result ~= tinyxml2 .XML_SUCCESS then
155+ os.remove (output_file )
156+ return input_file
157+ end
158+ local score_partwise = musicxml :FirstChildElement (" score-partwise" )
159+ if not score_partwise then
160+ os.remove (output_file )
161+ return input_file
162+ end
163+ process_xml (score_partwise )
164+ musicxml :SaveFile (output_file )
165+ return " "
166+ end
167+ function process_directory (path_name )
168+ local folder_dialog = finale .FCFolderBrowseDialog (finenv .UI ())
169+ folder_dialog :SetWindowTitle (finale .FCString (" Select Folder of MusicXML Files:" ))
170+ folder_dialog :SetFolderPath (path_name )
171+ if not folder_dialog :Execute () then
172+ return nil
173+ end
174+ local selected_directory = finale .FCString ()
175+ folder_dialog :GetFolderPath (selected_directory )
176+ local src_dir = selected_directory .LuaString
163177
164- local new_filepath = path .. filename .. " massaged." .. extension
165- return new_filepath
178+ local error_list = {}
179+ local lfs = require (" lfs" )
180+ for file in lfs .dir (src_dir ) do
181+ if file ~= " ." and file ~= " .." and file :sub (- xml_extension :len ()) == xml_extension then
182+ local file_error = process_one_file (src_dir .. " /" .. file )
183+ if file_error ~= " " then
184+ table.insert (error_list , file_error )
185+ end
186+ end
187+ end
188+ if # error_list > 0 then
189+ alert_error (error_list )
190+ end
191+ end
192+ function do_open_dialog (path_name )
193+ local open_dialog = finale .FCFileOpenDialog (finenv .UI ())
194+ open_dialog :SetWindowTitle (finale .FCString (" Select a MusicXML File:" ))
195+ open_dialog :AddFilter (finale .FCString (" *" .. xml_extension ), finale .FCString (" MusicXML File" ))
196+ open_dialog :SetInitFolder (path_name )
197+ open_dialog :AssureFileExtension (xml_extension )
198+ if not open_dialog :Execute () then
199+ return nil
200+ end
201+ local selected_file_name = finale .FCString ()
202+ open_dialog :GetFileName (selected_file_name )
203+ return selected_file_name .LuaString
166204end
167205function music_xml_massage_export ()
168206 local documents = finale .FCDocuments ()
169207 documents :LoadAll ()
170208 local document = documents :FindCurrent ()
171- local xml_file = do_open_dialog (document )
172- if not xml_file then
173- return
174- end
175- local output_name = append_massaged_to_filename (xml_file )
176-
177- remove_processing_instructions (xml_file , output_name )
178- local musicxml = tinyxml2 .XMLDocument ()
179- local result = musicxml :LoadFile (output_name )
180- if result ~= tinyxml2 .XML_SUCCESS then
181- error (" Unable to process " .. xml_file .. " . " .. musicxml :ErrorStr ())
182- return
209+ local path_name = finale .FCString ()
210+ if document then
211+ document :GetPath (path_name )
212+ path_name :SplitToPathAndFile (path_name , nil )
183213 end
184- local score_partwise = musicxml :FirstChildElement (" score-partwise" )
185- if not score_partwise then
186- error (" File " .. xml_file .. " does not appear to be a Finale-exported MusicXML file." )
214+ if do_single_file then
215+ local xml_file = do_open_dialog (path_name )
216+ if xml_file then
217+ local file_error = process_one_file (xml_file )
218+ if file_error ~= " " then
219+ alert_error {file_error }
220+ end
221+ end
222+ else
223+ process_directory (path_name )
187224 end
188- process_xml (score_partwise )
189- musicxml :SaveFile (output_name )
190- finenv .UI ():AlertInfo (" Processed to " .. output_name .. " ." , " Processed File" )
191225end
192226music_xml_massage_export ()
0 commit comments