2017-06-07 20:47:10 +00:00
|
|
|
/*jshint esversion: 6 */
|
2021-05-30 14:07:09 +00:00
|
|
|
/**
|
2021-06-28 09:23:41 +00:00
|
|
|
* https://regex101.com/r/XwBLUH/2
|
2021-05-30 14:07:09 +00:00
|
|
|
* This regex parses out the following groups:
|
2021-06-28 09:23:41 +00:00
|
|
|
* trackl track number at the left of the timestamp, optional and optionally enclosed in square brackets or parantheses
|
|
|
|
* trackr track number at the of the timestamp, optional and optionally enclosed in square brackets or parantheses
|
|
|
|
*
|
2021-05-30 14:07:09 +00:00
|
|
|
* start_ts: complete track start timestamp (hh:mm:ss) (mm:ss is minimum)
|
|
|
|
* start_hh: starting hh, optional
|
|
|
|
* start_mm: starting minutes, required
|
|
|
|
* start_ss: starting seconds, required
|
|
|
|
*
|
|
|
|
* end_ts: complete track end timestamp (hh:mm:ss) (mm:ss is minimum to match). optional
|
|
|
|
* end:hh: track end hour, optional
|
|
|
|
* end:mm: track end minute, optional
|
|
|
|
* end:ss: track end seconds, optional
|
|
|
|
*
|
2021-06-28 09:23:41 +00:00
|
|
|
* text_1: text found to the left of the timestamp, ignoring the track number
|
|
|
|
* text_2: text found to the right of the timestamp, ignoring the track number
|
2021-05-30 14:07:09 +00:00
|
|
|
*
|
|
|
|
* It is suggested to check their lengths and pick one to parse as the Track Title
|
|
|
|
*/
|
2021-06-28 09:23:41 +00:00
|
|
|
const TS_REGEX = /^((?<trackl>\d{1,3})\.)? *(?<text_1>.*?) *[\(\[]?(?<start_ts>((?<start_hh>\d{1,2}):)?(?<start_mm>\d{1,2}):(?<start_ss>\d{1,2})) *-? *[\)\]]?(?<end_ts>(?<end_hh>\d{1,2}:)?(?<end_mm>\d{1,2}):(?<end_ss>\d{1,2}))? *((?<trackr>\d{1,3})\.)? *(?<text_2>.*?)$/;
|
2021-07-21 07:50:19 +00:00
|
|
|
import getArtistTitle from "get-artist-title";
|
2017-06-10 20:23:12 +00:00
|
|
|
var _options = {};
|
2017-06-07 20:14:09 +00:00
|
|
|
|
2021-07-21 07:50:19 +00:00
|
|
|
// Returns number of total seconds
|
|
|
|
function convertTime(h, m, s) {
|
|
|
|
return +h * 60 * 60 + +m * 60 + +s;
|
2021-05-30 15:31:27 +00:00
|
|
|
}
|
|
|
|
|
2021-07-21 07:50:19 +00:00
|
|
|
// Only picks out lines which have a timestamp in them
|
2017-06-07 20:14:09 +00:00
|
|
|
var filterTimestamp = function(line) {
|
2021-07-21 07:50:19 +00:00
|
|
|
return TS_REGEX.test(line);
|
2017-06-07 20:14:09 +00:00
|
|
|
};
|
|
|
|
|
2021-07-21 07:50:19 +00:00
|
|
|
// Parse each line as per the regex
|
2021-05-30 15:31:27 +00:00
|
|
|
var firstPass = function(line) {
|
2017-06-07 20:14:09 +00:00
|
|
|
let matches = line.match(TS_REGEX);
|
2021-07-21 07:50:19 +00:00
|
|
|
let track = matches.groups["trackl"]
|
|
|
|
? +matches.groups["trackl"]
|
|
|
|
: matches.groups["trackr"]
|
|
|
|
? +matches.groups["trackr"]
|
|
|
|
: null;
|
2017-06-07 20:14:09 +00:00
|
|
|
return {
|
2021-06-28 09:23:41 +00:00
|
|
|
track: track,
|
2021-05-30 14:07:09 +00:00
|
|
|
start: {
|
2021-07-21 07:50:19 +00:00
|
|
|
ts:
|
|
|
|
matches.groups["start_ts"].length < 6
|
|
|
|
? `00:${matches.groups["start_ts"]}`
|
|
|
|
: matches.groups["start_ts"],
|
|
|
|
hh: matches.groups["start_hh"] ? +matches.groups["start_hh"] : 0,
|
2021-05-30 14:07:09 +00:00
|
|
|
// These 2 are always set
|
2021-07-21 07:50:19 +00:00
|
|
|
mm: +matches.groups["start_mm"],
|
|
|
|
ss: +matches.groups["start_ss"],
|
2021-05-30 14:07:09 +00:00
|
|
|
},
|
2021-07-21 07:50:19 +00:00
|
|
|
end:
|
|
|
|
matches.groups["end_ts"] !== undefined
|
|
|
|
? {
|
|
|
|
ts: matches.groups["end_ts"] ? matches.groups["end_ts"] : null,
|
|
|
|
hh: matches.groups["end_hh"] ? +matches.groups["end_hh"] : null,
|
|
|
|
mm: matches.groups["end_mm"] ? +matches.groups["end_mm"] : null,
|
|
|
|
ss: matches.groups["end_ss"] ? +matches.groups["end_ss"] : null,
|
|
|
|
}
|
|
|
|
: null,
|
2021-05-30 14:07:09 +00:00
|
|
|
_: {
|
2021-07-21 07:50:19 +00:00
|
|
|
left_text: matches.groups["text_1"],
|
|
|
|
right_text: matches.groups["text_2"],
|
|
|
|
},
|
|
|
|
};
|
2017-06-07 20:14:09 +00:00
|
|
|
};
|
|
|
|
|
2021-07-21 07:50:19 +00:00
|
|
|
// Add a calc attribute with total seconds
|
2021-05-30 14:07:09 +00:00
|
|
|
var calcTimestamp = function(obj) {
|
2021-07-21 07:50:19 +00:00
|
|
|
if (obj.end) {
|
|
|
|
obj.end.calc = convertTime(obj.end.hh, obj.end.mm, obj.end.ss);
|
2021-05-30 14:07:09 +00:00
|
|
|
}
|
2021-07-21 07:50:19 +00:00
|
|
|
obj.start.calc = convertTime(obj.start.hh, obj.start.mm, obj.start.ss);
|
|
|
|
return obj;
|
|
|
|
};
|
2017-06-07 20:14:09 +00:00
|
|
|
|
2021-07-21 07:50:19 +00:00
|
|
|
// Pick the longer "text" from left or right side.
|
2021-05-30 14:07:09 +00:00
|
|
|
var parseTitle = function(obj) {
|
2021-07-21 07:50:19 +00:00
|
|
|
obj.title =
|
|
|
|
obj._.left_text.length > obj._.right_text.length
|
|
|
|
? obj._.left_text
|
|
|
|
: obj._.right_text;
|
|
|
|
return obj;
|
|
|
|
};
|
2017-06-07 20:14:09 +00:00
|
|
|
|
2021-07-21 07:50:19 +00:00
|
|
|
// Parse the text as the title/artist
|
2017-06-10 20:23:12 +00:00
|
|
|
var parseArtist = function(obj) {
|
|
|
|
let [artist, title] = getArtistTitle(obj.title, {
|
|
|
|
defaultArtist: _options.artist,
|
2021-07-21 07:50:19 +00:00
|
|
|
defaultTitle: obj.title,
|
2017-06-10 20:23:12 +00:00
|
|
|
});
|
2021-07-21 07:50:19 +00:00
|
|
|
obj.artist = artist;
|
|
|
|
obj.title = title;
|
|
|
|
return obj;
|
2017-06-10 20:23:12 +00:00
|
|
|
};
|
|
|
|
|
2021-07-21 07:50:19 +00:00
|
|
|
// If track numbers are not present, add them accordingly
|
2021-05-30 15:31:27 +00:00
|
|
|
var addTrack = function(obj, index) {
|
2021-07-21 07:50:19 +00:00
|
|
|
if (obj.track == null) {
|
|
|
|
obj.track = index + 1;
|
2021-05-30 15:31:27 +00:00
|
|
|
}
|
2021-07-21 07:50:19 +00:00
|
|
|
return obj;
|
|
|
|
};
|
2021-05-30 15:31:27 +00:00
|
|
|
|
2021-07-21 07:50:19 +00:00
|
|
|
// Add "end" timestamps as next start timestamps
|
2021-05-30 15:31:27 +00:00
|
|
|
var addEnd = function(obj, index, arr) {
|
|
|
|
if (!obj.end) {
|
2021-07-21 07:50:19 +00:00
|
|
|
if (arr.length != index + 1) {
|
|
|
|
let next = arr[index + 1];
|
|
|
|
obj.end = next.start;
|
|
|
|
return obj;
|
2021-05-30 15:31:27 +00:00
|
|
|
}
|
|
|
|
}
|
2021-07-21 07:50:19 +00:00
|
|
|
return obj;
|
|
|
|
};
|
|
|
|
|
|
|
|
var timeToObject = function(obj) {
|
|
|
|
let d = new Date(obj.calc * 1000).toISOString();
|
|
|
|
obj.hh = +d.substr(11, 2);
|
|
|
|
obj.mm = +d.substr(14, 2);
|
|
|
|
obj.ss = +d.substr(17, 2);
|
|
|
|
obj.ts = d.substr(11, 8);
|
|
|
|
return obj;
|
|
|
|
};
|
|
|
|
|
2021-07-21 08:00:42 +00:00
|
|
|
// Instead of timestamps, some tracklists use durations
|
|
|
|
// If durations are provided, use them to re-calculate
|
|
|
|
// the starting and ending timestamps
|
2021-07-21 07:50:19 +00:00
|
|
|
var fixDurations = function(list) {
|
|
|
|
for (let i in list) {
|
|
|
|
if (i == 0) {
|
|
|
|
// Set the first one to start of track.
|
|
|
|
list[i].start.hh = list[i].start.mm = list[i].start.ss = 0;
|
|
|
|
// And end at the right time
|
|
|
|
list[i].end = { calc: list[i].start.calc };
|
2021-07-21 08:14:47 +00:00
|
|
|
list[i].start.calc = 0;
|
2021-07-21 07:50:19 +00:00
|
|
|
} else {
|
|
|
|
// All the others tracks start at the end of the previous one
|
|
|
|
// And end at start time + duration
|
|
|
|
let previous = list[i - 1];
|
|
|
|
list[i].end = { calc: previous.end.calc + list[i].start.calc };
|
|
|
|
list[i].start.calc = previous.end.calc;
|
|
|
|
}
|
|
|
|
|
|
|
|
list[i].start = timeToObject(list[i].start);
|
|
|
|
list[i].end = timeToObject(list[i].end);
|
|
|
|
}
|
|
|
|
};
|
2021-05-30 15:31:27 +00:00
|
|
|
|
2021-07-21 08:14:47 +00:00
|
|
|
export function parse(
|
|
|
|
text,
|
|
|
|
options = { artist: "Unknown", forceTimestamps: false, forceDurations: false }
|
|
|
|
) {
|
2021-05-30 15:31:27 +00:00
|
|
|
_options = options;
|
2021-07-21 07:50:19 +00:00
|
|
|
let durations = false;
|
|
|
|
let result = text
|
|
|
|
.split("\n")
|
2021-05-30 15:31:27 +00:00
|
|
|
.filter(filterTimestamp)
|
|
|
|
.map(firstPass)
|
2021-07-21 07:50:19 +00:00
|
|
|
.map(calcTimestamp);
|
|
|
|
|
2021-07-21 08:14:47 +00:00
|
|
|
if (!options.forceTimestamps) {
|
2021-07-21 08:00:42 +00:00
|
|
|
// If our timestamps are not in increasing order
|
|
|
|
// Assume that we've been given a duration list instead
|
|
|
|
result.forEach((current, index, list) => {
|
|
|
|
if (index > 0) {
|
|
|
|
let previous = list[index - 1];
|
|
|
|
if (current.start.calc < previous.start.calc) {
|
|
|
|
durations = true;
|
|
|
|
}
|
2021-07-21 07:50:19 +00:00
|
|
|
}
|
2021-07-21 08:00:42 +00:00
|
|
|
});
|
2021-07-21 07:50:19 +00:00
|
|
|
|
2021-07-21 08:14:47 +00:00
|
|
|
if (durations || options.forceDurations == true) {
|
2021-07-21 08:00:42 +00:00
|
|
|
fixDurations(result);
|
|
|
|
}
|
2021-07-21 07:50:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
2021-05-30 15:31:27 +00:00
|
|
|
.map(parseTitle)
|
|
|
|
.map(parseArtist)
|
|
|
|
.map(addTrack)
|
2021-07-21 07:50:19 +00:00
|
|
|
.map(addEnd);
|
2021-05-30 15:31:27 +00:00
|
|
|
}
|