diff --git a/index.js b/index.js index 441756e..0aa84b2 100755 --- a/index.js +++ b/index.js @@ -1,16 +1,16 @@ #!/usr/bin/env node -import ytdl from 'ytdl-core'; -import getArtistTitle from 'get-artist-title' -import {parse} from './src/parser.js' -import {generate} from './src/cue.js' -import minimist from 'minimist' -import exit from 'process' +import ytdl from "ytdl-core"; +import getArtistTitle from "get-artist-title"; +import { parse } from "./src/parser.js"; +import { generate } from "./src/cue.js"; +import minimist from "minimist"; +import exit from "process"; let argv = minimist(process.argv.slice(2), { - string: 'audio-file' + string: "audio-file", }); -if (argv._.length <1 || argv.help ){ +if (argv._.length < 1 || argv.help) { console.log(`Usage $ youtube-cue [--audio-file audio.m4a] [output_file] @@ -36,32 +36,38 @@ if (argv._.length <1 || argv.help ){ $ youtube-cue --audio-file audio.m4a "https://www.youtube.com/watch?v=THzUassmQwE" "T A Y L O R S W I F T – Folklore [Full album].cue" saved $ youtube-cue "https://youtu.be/THzUassmQwE" folklore.cue - folklore.cue saved`) + folklore.cue saved`); } else { - let url = argv._[0] + let url = argv._[0]; - ytdl.getInfo(url).then(info=>{ - let audioFile = argv['audio-file']? argv['audio-file'] : `${info.videoDetails.title}.m4a` + ytdl.getInfo(url).then((info) => { + let audioFile = argv["audio-file"] + ? argv["audio-file"] + : `${info.videoDetails.title}.m4a`; - let output_file = argv._[1]? argv._[1] : `${info.videoDetails.title}.cue` + let output_file = argv._[1] ? argv._[1] : `${info.videoDetails.title}.cue`; - let forceTimestamps = argv['timestamps']? argv['timestamps'] : false; + let forceTimestamps = argv["timestamps"] ? argv["timestamps"] : false; - let forceDurations = argv['durations']? argv['durations'] : false; + let forceDurations = argv["durations"] ? argv["durations"] : false; if (forceTimestamps && forceDurations) { console.error("You can't pass both --timestamps and durations"); - exit(1) + exit(1); } - let res = getArtistTitle(info.videoDetails.title,{ + let res = getArtistTitle(info.videoDetails.title, { defaultArtist: "Unknown Artist", - defaultTitle: info.videoDetails.title + defaultTitle: info.videoDetails.title, }); - let [artist, album] = res - artist = (info.videoDetails.media ? info.videoDetails.media.artist : artist) - let tracks = parse(info.videoDetails.description, {artist, forceTimestamps, forceDurations}) - generate({tracks, artist, audioFile, album}, output_file) - console.log(`"${output_file}" saved`) - }) + let [artist, album] = res; + artist = info.videoDetails.media ? info.videoDetails.media.artist : artist; + let tracks = parse(info.videoDetails.description, { + artist, + forceTimestamps, + forceDurations, + }); + generate({ tracks, artist, audioFile, album }, output_file); + console.log(`"${output_file}" saved`); + }); } diff --git a/src/cue.js b/src/cue.js index 718cae1..df1a630 100644 --- a/src/cue.js +++ b/src/cue.js @@ -1,21 +1,24 @@ -import fs from 'fs'; +import fs from "fs"; // https://en.wikipedia.org/wiki/Cue_sheet_(computing) export function generate(data, outputFile) { try { - fs.truncateSync(outputFile) + fs.truncateSync(outputFile); } catch {} fs.appendFileSync(outputFile, `REM Generated using youtube-cue\n`); fs.appendFileSync(outputFile, `PERFORMER "${data.artist}"\n`); fs.appendFileSync(outputFile, `TITLE "${data.album}"\n`); fs.appendFileSync(outputFile, `FILE "${data.audioFile}" M4A\n`); - for(var i in data.tracks) { + for (var i in data.tracks) { let song = data.tracks[i]; - let minutes = (song.start.hh * 60) + (song.start.mm) + let minutes = song.start.hh * 60 + song.start.mm; fs.appendFileSync(outputFile, ` TRACK ${song.track} AUDIO\n`); fs.appendFileSync(outputFile, ` TITLE "${song.title}"\n`); fs.appendFileSync(outputFile, ` PERFORMER "${song.artist}"\n`); // Cue File is always MINUTES:SECONDS:FRAME, where FRAME is 00 - fs.appendFileSync(outputFile, ` INDEX 01 ${minutes}:${song.start.ss}:00\n`); + fs.appendFileSync( + outputFile, + ` INDEX 01 ${minutes}:${song.start.ss}:00\n` + ); } } diff --git a/src/parser.js b/src/parser.js index 5e4b701..ad966d3 100644 --- a/src/parser.js +++ b/src/parser.js @@ -20,7 +20,8 @@ * * It is suggested to check their lengths and pick one to parse as the Track Title */ -const TS_REGEX = /^((?\d{1,3})\.)? *(?.*?) *[\(\[]?(?((?\d{1,2}):)?(?\d{1,2}):(?\d{1,2})) *-? *[\)\]]?(?(?\d{1,2}:)?(?\d{1,2}):(?\d{1,2}))? *((?\d{1,3})\.)? *(?.*?)$/; +const TS_REGEX = + /^((?\d{1,3})\.)? *(?.*?) *[\(\[]?(?((?\d{1,2}):)?(?\d{1,2}):(?\d{1,2})) *-? *[\)\]]?(?(?\d{1,2}:)?(?\d{1,2}):(?\d{1,2}))? *((?\d{1,3})\.)? *(?.*?)$/; import getArtistTitle from "get-artist-title"; var _options = {}; @@ -30,12 +31,12 @@ function convertTime(h, m, s) { } // Only picks out lines which have a timestamp in them -var filterTimestamp = function(line) { +var filterTimestamp = function (line) { return TS_REGEX.test(line); }; // Parse each line as per the regex -var firstPass = function(line) { +var firstPass = function (line) { let matches = line.match(TS_REGEX); let track = matches.groups["trackl"] ? +matches.groups["trackl"] @@ -71,7 +72,7 @@ var firstPass = function(line) { }; // Add a calc attribute with total seconds -var calcTimestamp = function(obj) { +var calcTimestamp = function (obj) { if (obj.end) { obj.end.calc = convertTime(obj.end.hh, obj.end.mm, obj.end.ss); } @@ -80,7 +81,7 @@ var calcTimestamp = function(obj) { }; // Pick the longer "text" from left or right side. -var parseTitle = function(obj) { +var parseTitle = function (obj) { obj.title = obj._.left_text.length > obj._.right_text.length ? obj._.left_text @@ -89,7 +90,7 @@ var parseTitle = function(obj) { }; // Parse the text as the title/artist -var parseArtist = function(obj) { +var parseArtist = function (obj) { let [artist, title] = getArtistTitle(obj.title, { defaultArtist: _options.artist, defaultTitle: obj.title, @@ -100,7 +101,7 @@ var parseArtist = function(obj) { }; // If track numbers are not present, add them accordingly -var addTrack = function(obj, index) { +var addTrack = function (obj, index) { if (obj.track == null) { obj.track = index + 1; } @@ -108,7 +109,7 @@ var addTrack = function(obj, index) { }; // Add "end" timestamps as next start timestamps -var addEnd = function(obj, index, arr) { +var addEnd = function (obj, index, arr) { if (!obj.end) { if (arr.length != index + 1) { let next = arr[index + 1]; @@ -119,7 +120,7 @@ var addEnd = function(obj, index, arr) { return obj; }; -var timeToObject = function(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); @@ -131,7 +132,7 @@ var timeToObject = function(obj) { // Instead of timestamps, some tracklists use durations // If durations are provided, use them to re-calculate // the starting and ending timestamps -var fixDurations = function(list) { +var fixDurations = function (list) { for (let i in list) { if (i == 0) { // Set the first one to start of track. @@ -167,7 +168,7 @@ export function parse( if (!options.forceTimestamps) { // If our timestamps are not in increasing order // Assume that we've been given a duration list instead - if (result[0].start.calc!=0) { + if (result[0].start.calc != 0) { result.forEach((current, index, list) => { if (index > 0) { let previous = list[index - 1]; @@ -183,9 +184,5 @@ export function parse( } } - return result - .map(parseTitle) - .map(parseArtist) - .map(addTrack) - .map(addEnd); + return result.map(parseTitle).map(parseArtist).map(addTrack).map(addEnd); } diff --git a/test/parser_test.js b/test/parser_test.js index bb3e592..3085c67 100644 --- a/test/parser_test.js +++ b/test/parser_test.js @@ -24,21 +24,21 @@ Hello World 48:20 const TEXT_WITH_ARTIST = "12:23 Rolling Stones - Hello World"; -describe("Parser", function() { +describe("Parser", function () { var big_result; - before(function() { + before(function () { big_result = parse(TEXT); }); - it("should find all timestamps", function() { + it("should find all timestamps", function () { assert.equal(big_result.length, 15); }); - it("should find artist names", function() { + it("should find artist names", function () { let result = parse(TEXT_WITH_ARTIST); assert.equal(result[0].artist, "Rolling Stones"); }); - it("should find track numbers", function() { + it("should find track numbers", function () { assert.equal(big_result[3].track, 1); assert.equal(big_result[4].track, 2); assert.equal(big_result[5].track, 3); @@ -51,7 +51,7 @@ describe("Parser", function() { assert.equal(big_result[12].track, 10); }); - it("should ensure ending timestamps for all", function() { + it("should ensure ending timestamps for all", function () { assert.deepEqual(big_result[13].end, { calc: 3023, hh: 0, @@ -63,8 +63,9 @@ describe("Parser", function() { assert.deepEqual(big_result[14].end, null); }); - it("should parse timestamps with square brackets", function() { - let result = parse(`[00:00:00] 1. Steve Kroeger x Skye Holland - Through The Dark + it("should parse timestamps with square brackets", function () { + let result = + parse(`[00:00:00] 1. Steve Kroeger x Skye Holland - Through The Dark [00:02:53] 2. Gabri Ponte x Jerome - Lonely `); assert.deepEqual(result[0], { artist: "Steve Kroeger x Skye Holland", @@ -79,7 +80,7 @@ describe("Parser", function() { }); }); - it("should parse durations when given", function() { + it("should parse durations when given", function () { let result = parse(`1. Artist - Title 6:19 2. Another Artist - Another Title 6:59 3. Yet Another Artist - Yet another title 5:12`); @@ -113,7 +114,7 @@ describe("Parser", function() { }); }); - it("should parse as timestamps if first timestamp is 00:00", function() { + it("should parse as timestamps if first timestamp is 00:00", function () { let result = parse(`1. Artist - Title 00:00 2. Another Artist - Another Title 01:00 3. Yet Another Artist - Yet another title 02:00`); @@ -140,11 +141,14 @@ describe("Parser", function() { track: 3, end: null, start: { ts: "00:02:00", hh: 0, mm: 2, ss: 0, calc: 120 }, - _: { left_text: "Yet Another Artist - Yet another title", right_text: "" }, + _: { + left_text: "Yet Another Artist - Yet another title", + right_text: "", + }, }); }); - it("should parse durations as timestamps when forced", function() { + it("should parse durations as timestamps when forced", function () { let result = parse( `1. Artist - Title 5:00 2. Another Artist - Another Title 4:20`, @@ -166,7 +170,7 @@ describe("Parser", function() { }); }); - it("should parse timestamps as durations when forced", function() { + it("should parse timestamps as durations when forced", function () { let result = parse( `1. Artist - Title 1:00 2. Another Artist - Another Title 1:15`, @@ -214,7 +218,7 @@ describe("Parser", function() { }); }); - it("should parse taylor swift", function() { + it("should parse taylor swift", function () { let result = parse(`0:00 the 1 3:29 cardigan 9:30 the last great american dynasty