propresenter-parser
Version:
Parses ProPresenter 4, 5, and 6 files to extract the data, and can build ProPresenter 5 and 6 files
237 lines (236 loc) • 9.21 kB
JavaScript
import { XMLBuilder } from 'fast-xml-parser';
import { Base64 } from 'js-base64';
import { IProTransitionType } from '../shared.model';
import * as Utils from '../utils';
export class v5Builder {
xmlBuilder;
options;
defaultTransitionObj = {
'@transitionType': IProTransitionType.None,
'@transitionDuration': 1,
'@motionEnabled': 0,
'@motionDuration': 20,
'@motionSpeed': 100,
};
constructor(options) {
this.xmlBuilder = new XMLBuilder({
attributeNamePrefix: '@',
format: true,
ignoreAttributes: false,
processEntities: false,
suppressUnpairedNode: false,
unpairedTags: [
'arrangements',
'timeCues',
'mediaTracks',
'bibleReference',
'cues',
'_-RVProTransitionObject-_transitionObject',
'_-RVRect3D-_position',
'NSColor',
'NSNumber',
'NSMutableString',
],
});
this.options = options;
const defaultProperties = {
album: '',
artist: '',
author: '',
category: 'Song',
ccliDisplay: false,
notes: '',
publisher: '',
height: 720,
width: 1280,
};
this.options.properties = { ...defaultProperties, ...this.options.properties };
const defaultSlideTextFormatting = {
textColor: { r: 255, g: 255, b: 255 },
textPadding: 20,
};
this.options.slideTextFormatting = {
...defaultSlideTextFormatting,
...this.options.slideTextFormatting,
};
}
build() {
const documentObj = {
RVPresentationDocument: {
'@CCLIArtistCredits': this.options.properties.artist,
'@CCLICopyrightInfo': this.options.properties.copyrightYear ?? '',
'@CCLIDisplay': this.options.properties.ccliDisplay ? 1 : 0,
'@CCLILicenseNumber': this.options.properties.ccliNumber ?? '',
'@CCLIPublisher': this.options.properties.publisher,
'@CCLISongTitle': this.options.properties.title,
'@album': this.options.properties.album,
'@artist': this.options.properties.artist,
'@author': this.options.properties.author,
'@category': this.options.properties.category,
'@notes': this.options.properties.notes,
'@lastDateUsed': Utils.getIsoDateString(),
'@height': this.options.properties.height,
'@width': this.options.properties.width,
'@backgroundColor': '0 0 0 1',
'@creatorCode': 0,
'@chordChartPath': '',
'@docType': 0,
'@drawingBackgroundColor': 0,
'@resourcesDirectory': '',
'@usedCount': 0,
'@versionNumber': 500,
timeline: {
'@timeOffSet': 0,
'@selectedMediaTrackIndex': 0,
'@unitOfMeasure': 60,
'@duration': 0,
'@loop': 0,
timeCues: {
'@containerClass': 'NSMutableArray',
},
mediaTracks: {
'@containerClass': 'NSMutableArray',
},
},
bibleReference: {
'@containerClass': 'NSMutableDictionary',
},
'_-RVProTransitionObject-_transitionObject': this.getTransitions(),
groups: {
'@containerClass': 'NSMutableArray',
RVSlideGrouping: this.buildSlideGroups(),
},
arrangements: {
'@containerClass': 'NSMutableArray',
RVSongArrangement: [],
},
},
};
return this.xmlBuilder.build(documentObj).trim();
}
getTransitions() {
if (this.options.transitions) {
const transitionsCopy = { ...this.defaultTransitionObj };
transitionsCopy['@transitionDuration'] = this.options.transitions.duration;
transitionsCopy['@transitionType'] = this.options.transitions.type;
return transitionsCopy;
}
return this.defaultTransitionObj;
}
buildSlideGroups() {
const xmlSlideGroups = [];
for (let i = 0; i < this.options.slideGroups.length; i++) {
const group = this.options.slideGroups[i];
xmlSlideGroups.push({
'@name': group.label,
'@uuid': Utils.getUniqueID(),
'@color': Utils.normalizeColorToRgbaString(group.groupColor ?? '0 0 0 0'),
'@serialization-array-index': i,
slides: {
'@containerClass': 'NSMutableArray',
RVDisplaySlide: this.buildSlidesForGroup(group),
},
});
}
return xmlSlideGroups;
}
buildSlidesForGroup(thisGroup) {
const xmlSlides = [];
for (let i = 0; i < thisGroup.slides.length; i++) {
const slide = thisGroup.slides[i];
let highlightColor = '0 0 0 0';
let label = '';
let text;
if (typeof slide === 'string') {
text = slide;
}
else {
highlightColor = Utils.normalizeColorToRgbaString(slide.slideColor ?? highlightColor);
label = slide.label ?? '';
text = slide.text;
}
xmlSlides.push({
'@backgroundColor': '0 0 0 0',
'@enabled': 1,
'@highlightColor': highlightColor,
'@hotKey': '',
'@label': label,
'@notes': '',
'@slideType': 1,
'@sort_index': i,
'@UUID': Utils.getUniqueID(),
'@drawingBackgroundColor': 0,
'@chordChartPath': '',
'@serialization-array-index': i,
cues: {
'@containerClass': 'NSMutableArray',
},
displayElements: {
'@containerClass': 'NSMutableArray',
RVTextElement: [this.buildTextElement(text)],
},
'_-RVProTransitionObject-_transitionObject': this.defaultTransitionObj,
});
}
return xmlSlides;
}
buildTextElement(text) {
const rtfText = Utils.formatRtf(text, this.options.slideTextFormatting.fontName, this.options.slideTextFormatting.textSize, Utils.normalizeColorToRgbObj(this.options.slideTextFormatting.textColor));
return {
'@displayDelay': 0,
'@displayName': 'Default',
'@locked': 0,
'@persistent': 0,
'@typeID': 0,
'@fromTemplate': 0,
'@bezelRadius': 0,
'@drawingFill': 0,
'@drawingShadow': 1,
'@drawingStroke': 0,
'@fillColor': '1 1 1 1',
'@rotation': 0,
'@source': '',
'@adjustsHeightToFit': 0,
'@verticalAlignment': 0,
'@RTFData': Base64.encode(rtfText),
'@revealType': 0,
'@serialization-array-index': 0,
stroke: {
'@containerClass': 'NSMutableDictionary',
NSColor: {
'@serialization-native-value': '0 0 0 1',
'@serialization-dictionary-key': 'RVShapeElementStrokeColorKey',
},
NSNumber: {
'@serialization-native-value': 1,
'@serialization-dictionary-key': 'RVShapeElementStrokeWidthKey',
},
},
'_-D-_serializedShadow': {
'@containerClass': 'NSMutableDictionary',
NSMutableString: {
'@serialization-native-value': `{3.4641016, -2}`,
'@serialization-dictionary-key': 'shadowOffset',
},
NSNumber: {
'@serialization-native-value': 4,
'@serialization-dictionary-key': 'shadowBlurRadius',
},
NSColor: {
'@serialization-native-value': '0 0 0 1',
'@serialization-dictionary-key': 'shadowColor',
},
},
'_-RVRect3D-_position': this.getElementPosition(),
};
}
getElementPosition() {
return {
'@x': this.options.slideTextFormatting.textPadding,
'@y': this.options.slideTextFormatting.textPadding,
'@z': 0,
'@width': this.options.properties.width - this.options.slideTextFormatting.textPadding * 2,
'@height': this.options.properties.height - this.options.slideTextFormatting.textPadding * 2,
};
}
}