From 3fdb7739ff10e4b9b7ae3a8cdd7f4f560862d5ab Mon Sep 17 00:00:00 2001 From: Nemo Date: Wed, 21 Jul 2021 12:08:04 +0530 Subject: [PATCH 1/9] Add testcase for parsing duration Ref #68 --- test/parser_test.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/parser_test.js b/test/parser_test.js index b4c3c12..67ef90f 100644 --- a/test/parser_test.js +++ b/test/parser_test.js @@ -76,6 +76,20 @@ describe("Parser", 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`) + assert.deepEqual(result[0], { + artist: "Artist", + title: "Title", + track: 1, + start: { ts: "00:00:00", hh: 0, mm: 0, ss: 0, calc: 0 }, + end: { ts: "00:06:19", hh: 0, mm: 6, ss: 19, calc: 379 }, + _: { left_text: "Artist - Title", right_text: "" }, + }) + }); + it("should parse taylor swift", function() { let result = parse(`0:00 the 1 3:29 cardigan From deca37893735a9c6e79e050a238850d4be58c344 Mon Sep 17 00:00:00 2001 From: Nemo Date: Wed, 21 Jul 2021 13:20:19 +0530 Subject: [PATCH 2/9] Parse durations correctly --- src/parser.js | 161 +++++++++++++++++++++++++++++++------------- test/parser_test.js | 29 ++++++-- 2 files changed, 137 insertions(+), 53 deletions(-) diff --git a/src/parser.js b/src/parser.js index 10b9c09..2ab6f0d 100644 --- a/src/parser.js +++ b/src/parser.js @@ -21,93 +21,160 @@ * 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})\.)? *(?.*?)$/; -import getArtistTitle from 'get-artist-title' +import getArtistTitle from "get-artist-title"; var _options = {}; -function convertTime(h,m,s) { - return (+h) * 60 * 60 + (+m) * 60 + (+s) +// Returns number of total seconds +function convertTime(h, m, s) { + return +h * 60 * 60 + +m * 60 + +s; } +// Only picks out lines which have a timestamp in them var filterTimestamp = function(line) { - return TS_REGEX.test(line) + return TS_REGEX.test(line); }; +// Parse each line as per the regex var firstPass = function(line) { let matches = line.match(TS_REGEX); - let track = matches.groups['trackl'] ? +matches.groups['trackl'] : (matches.groups['trackr'] ? +matches.groups['trackr'] : null) + let track = matches.groups["trackl"] + ? +matches.groups["trackl"] + : matches.groups["trackr"] + ? +matches.groups["trackr"] + : null; return { track: track, start: { - 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, + 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, // These 2 are always set - mm: +matches.groups['start_mm'], - ss: +matches.groups['start_ss'], + mm: +matches.groups["start_mm"], + ss: +matches.groups["start_ss"], }, - 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), + 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, _: { - left_text: matches.groups['text_1'], - right_text: matches.groups['text_2'] - } - } + left_text: matches.groups["text_1"], + right_text: matches.groups["text_2"], + }, + }; }; +// Add a calc attribute with total seconds var calcTimestamp = function(obj) { - if(obj.end) { - obj.end.calc = convertTime(obj.end.hh,obj.end.mm,obj.end.ss) + if (obj.end) { + obj.end.calc = convertTime(obj.end.hh, obj.end.mm, obj.end.ss); } - obj.start.calc = convertTime(obj.start.hh,obj.start.mm,obj.start.ss) - return obj -} + obj.start.calc = convertTime(obj.start.hh, obj.start.mm, obj.start.ss); + return obj; +}; +// Pick the longer "text" from left or right side. var parseTitle = function(obj) { - obj.title = obj._.left_text.length > obj._.right_text.length - ? obj._.left_text : obj._.right_text; - return obj -} + obj.title = + obj._.left_text.length > obj._.right_text.length + ? obj._.left_text + : obj._.right_text; + return obj; +}; +// Parse the text as the title/artist var parseArtist = function(obj) { let [artist, title] = getArtistTitle(obj.title, { defaultArtist: _options.artist, - defaultTitle: obj.title + defaultTitle: obj.title, }); - obj.artist = artist - obj.title = title - return obj + obj.artist = artist; + obj.title = title; + return obj; }; +// If track numbers are not present, add them accordingly var addTrack = function(obj, index) { - if (obj.track==null) { - obj.track = index+1 + if (obj.track == null) { + obj.track = index + 1; } - return obj -} + return obj; +}; +// Add "end" timestamps as next start timestamps var addEnd = function(obj, index, arr) { if (!obj.end) { - if(arr.length!=index+1) { - let next = arr[index+1] - obj.end = next.start - return obj + if (arr.length != index + 1) { + let next = arr[index + 1]; + obj.end = next.start; + return obj; } } - return obj -} + return obj; +}; -export function parse (text, options = { artist: 'Unknown' }) { +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; +}; + +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 }; + list[i].start.calc = 0 + } 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); + } +}; + +export function parse(text, options = { artist: "Unknown" }) { _options = options; - return text - .split('\n') + let durations = false; + let result = text + .split("\n") .filter(filterTimestamp) .map(firstPass) - .map(calcTimestamp) + .map(calcTimestamp); + + result.forEach((current, index, list) => { + if (index > 0) { + let previous = list[index - 1]; + if (current.start.calc < previous.start.calc) { + durations = true; + } + } + }); + + if (durations) { + // console.error("Detected durations instead of timestamps. \nIf this is incorrect, pass the --timestamps-only flag and create an issue at \nhttps://github.com/captn3m0/youtube-cue/issues/new/choose") + fixDurations(result); + } + + return result .map(parseTitle) .map(parseArtist) .map(addTrack) - .map(addEnd) + .map(addEnd); } diff --git a/test/parser_test.js b/test/parser_test.js index 67ef90f..eaa875f 100644 --- a/test/parser_test.js +++ b/test/parser_test.js @@ -5,10 +5,10 @@ import { parse } from "../src/parser.js"; const TEXT = ` 00:40 The Coders - Hello World -12:23 This is not the end +1:00 This is not the end Something else in the middle -1:23:11 Not the last song -01. Screens 0:00 - 5:40 +1:23 Not the last song +01. Screens 1:40 - 5:40 02. Inharmonious Slog 5:40 - 10:11 03. The Everyday Push 10:11 - 15:46 04. Storm 15:46 - 19:07 @@ -77,9 +77,9 @@ describe("Parser", 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`) + let result = parse(`1. Artist - Title 6:19 +2. Another Artist - Another Title 6:59 +3. Yet Another Artist - Yet another title 5:12`) assert.deepEqual(result[0], { artist: "Artist", title: "Title", @@ -88,6 +88,23 @@ describe("Parser", function() { end: { ts: "00:06:19", hh: 0, mm: 6, ss: 19, calc: 379 }, _: { left_text: "Artist - Title", right_text: "" }, }) + + assert.deepEqual(result[1], { + artist: "Another Artist", + title: "Another Title", + track: 2, + start: { ts: "00:06:19", hh: 0, mm: 6, ss: 19, calc: 379 }, + end: { ts: "00:13:18", hh: 0, mm: 13, ss: 18, calc: 798 }, + _: { left_text: "Another Artist - Another Title", right_text: "" }, + }) + assert.deepEqual(result[2], { + artist: "Yet Another Artist", + title: "Yet another title", + track: 3, + start: { ts: "00:13:18", hh: 0, mm: 13, ss: 18, calc: 798 }, + end: { ts: "00:18:30", hh: 0, mm: 18, ss: 30, calc: 1110 }, + _: { left_text: "Yet Another Artist - Yet another title", right_text: "" }, + }) }); it("should parse taylor swift", function() { From b0fd29cbdb998b8f67d7d3f0807d627f5ff21f37 Mon Sep 17 00:00:00 2001 From: Nemo Date: Wed, 21 Jul 2021 13:22:32 +0530 Subject: [PATCH 3/9] [ci] Run tests on all supported node versions --- .github/workflows/action.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 8779e4d..0a3c917 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -1,13 +1,16 @@ on: push name: Main Workflow jobs: - runNpmStuff: + tests: + strategy: + matrix: + node: ['16', '14', '12'] name: Run NPM Stuff runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: actions/setup-node@v2 with: - node-version: '16' + node-version: ${{matrix.node}} - run: npm install - run: npm test From 10981c62ce7c8fbbf513f93ea448e6f6e217d661 Mon Sep 17 00:00:00 2001 From: Nemo Date: Wed, 21 Jul 2021 13:30:42 +0530 Subject: [PATCH 4/9] Force timestamps using --timestamps-only --- index.js | 6 +++- src/parser.js | 28 +++++++++------ test/parser_test.js | 88 +++++++++++++++++++++++++++++---------------- 3 files changed, 80 insertions(+), 42 deletions(-) diff --git a/index.js b/index.js index 6f59533..a8e0da3 100755 --- a/index.js +++ b/index.js @@ -22,6 +22,8 @@ if (argv._.length <1 || argv.help ){ where $VIDEOTITLE is the title of the YouTube video. + --timestamps-only Do not try to parse the timestamps as track durations + Examples $ 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 @@ -35,13 +37,15 @@ if (argv._.length <1 || argv.help ){ let output_file = argv._[1]? argv._[1] : `${info.videoDetails.title}.cue` + let timestampsOnly = argv['timestamps-only']? argv['timestamps-only'] : false; + let res = getArtistTitle(info.videoDetails.title,{ defaultArtist: "Unknown Artist", defaultTitle: info.videoDetails.title }); let [artist, album] = res artist = (info.videoDetails.media ? info.videoDetails.media.artist : artist) - let tracks = parse(info.videoDetails.description, {artist}) + let tracks = parse(info.videoDetails.description, {artist,timestampsOnly}) generate({tracks, artist, audioFile, album}, output_file) console.log(`"${output_file}" saved`) }) diff --git a/src/parser.js b/src/parser.js index 2ab6f0d..36609b3 100644 --- a/src/parser.js +++ b/src/parser.js @@ -128,6 +128,9 @@ var timeToObject = function(obj) { return 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) { for (let i in list) { if (i == 0) { @@ -149,7 +152,7 @@ var fixDurations = function(list) { } }; -export function parse(text, options = { artist: "Unknown" }) { +export function parse(text, options = { artist: "Unknown", timestampsOnly : false }) { _options = options; let durations = false; let result = text @@ -158,18 +161,21 @@ export function parse(text, options = { artist: "Unknown" }) { .map(firstPass) .map(calcTimestamp); - result.forEach((current, index, list) => { - if (index > 0) { - let previous = list[index - 1]; - if (current.start.calc < previous.start.calc) { - durations = true; + if (options.timestampsOnly == false) { + // 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; + } } - } - }); + }); - if (durations) { - // console.error("Detected durations instead of timestamps. \nIf this is incorrect, pass the --timestamps-only flag and create an issue at \nhttps://github.com/captn3m0/youtube-cue/issues/new/choose") - fixDurations(result); + if (durations) { + fixDurations(result); + } } return result diff --git a/test/parser_test.js b/test/parser_test.js index eaa875f..27fd294 100644 --- a/test/parser_test.js +++ b/test/parser_test.js @@ -65,46 +65,74 @@ describe("Parser", function() { 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 `) + [00:02:53] 2. Gabri Ponte x Jerome - Lonely `); assert.deepEqual(result[0], { - artist: "Steve Kroeger x Skye Holland", - title: "Through The Dark", - track: 1, - start: { ts: "00:00:00", hh: 0, mm: 0, ss: 0, calc: 0 }, - end: { ts: "00:02:53", hh: 0, mm: 2, ss: 53, calc: 173 }, - _: { left_text: "", right_text: "Steve Kroeger x Skye Holland - Through The Dark" }, - }) + artist: "Steve Kroeger x Skye Holland", + title: "Through The Dark", + track: 1, + start: { ts: "00:00:00", hh: 0, mm: 0, ss: 0, calc: 0 }, + end: { ts: "00:02:53", hh: 0, mm: 2, ss: 53, calc: 173 }, + _: { + left_text: "", + right_text: "Steve Kroeger x Skye Holland - Through The Dark", + }, + }); }); 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`) +3. Yet Another Artist - Yet another title 5:12`); assert.deepEqual(result[0], { - artist: "Artist", - title: "Title", - track: 1, - start: { ts: "00:00:00", hh: 0, mm: 0, ss: 0, calc: 0 }, - end: { ts: "00:06:19", hh: 0, mm: 6, ss: 19, calc: 379 }, - _: { left_text: "Artist - Title", right_text: "" }, - }) + artist: "Artist", + title: "Title", + track: 1, + start: { ts: "00:00:00", hh: 0, mm: 0, ss: 0, calc: 0 }, + end: { ts: "00:06:19", hh: 0, mm: 6, ss: 19, calc: 379 }, + _: { left_text: "Artist - Title", right_text: "" }, + }); assert.deepEqual(result[1], { - artist: "Another Artist", - title: "Another Title", - track: 2, - start: { ts: "00:06:19", hh: 0, mm: 6, ss: 19, calc: 379 }, - end: { ts: "00:13:18", hh: 0, mm: 13, ss: 18, calc: 798 }, - _: { left_text: "Another Artist - Another Title", right_text: "" }, - }) + artist: "Another Artist", + title: "Another Title", + track: 2, + start: { ts: "00:06:19", hh: 0, mm: 6, ss: 19, calc: 379 }, + end: { ts: "00:13:18", hh: 0, mm: 13, ss: 18, calc: 798 }, + _: { left_text: "Another Artist - Another Title", right_text: "" }, + }); assert.deepEqual(result[2], { - artist: "Yet Another Artist", - title: "Yet another title", - track: 3, - start: { ts: "00:13:18", hh: 0, mm: 13, ss: 18, calc: 798 }, - end: { ts: "00:18:30", hh: 0, mm: 18, ss: 30, calc: 1110 }, - _: { left_text: "Yet Another Artist - Yet another title", right_text: "" }, - }) + artist: "Yet Another Artist", + title: "Yet another title", + track: 3, + start: { ts: "00:13:18", hh: 0, mm: 13, ss: 18, calc: 798 }, + end: { ts: "00:18:30", hh: 0, mm: 18, ss: 30, calc: 1110 }, + _: { + left_text: "Yet Another Artist - Yet another title", + right_text: "", + }, + }); + }); + + it("should parse durations as timestamps when forced", function() { + let result = parse( + `1. Artist - Title 5:00 +2. Another Artist - Another Title 4:20`, + { timestampsOnly: true } + ); + assert.deepEqual(result[0].end, { + ts: "00:4:20", + hh: 0, + mm: 4, + ss: 20, + calc: 260, + }); + assert.deepEqual(result[1].start, { + ts: "00:4:20", + hh: 0, + mm: 4, + ss: 20, + calc: 260, + }); }); it("should parse taylor swift", function() { From 7247b7be66dca1c9c2825d1b35d14bb7e6821e34 Mon Sep 17 00:00:00 2001 From: Nemo Date: Wed, 21 Jul 2021 13:44:47 +0530 Subject: [PATCH 5/9] Add a --timestamps-are-durations flag --- index.js | 16 +++++++++++++-- src/parser.js | 11 ++++++---- test/parser_test.js | 50 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index a8e0da3..1aa9d3e 100755 --- a/index.js +++ b/index.js @@ -4,6 +4,7 @@ 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' @@ -23,6 +24,10 @@ if (argv._.length <1 || argv.help ){ where $VIDEOTITLE is the title of the YouTube video. --timestamps-only Do not try to parse the timestamps as track durations + --timestamps-are-durations Parse timestamps as durations + + The above 2 are only needed to force behaviour in very specific edge cases, they should + not be required for most files. Examples $ youtube-cue --audio-file audio.m4a "https://www.youtube.com/watch?v=THzUassmQwE" @@ -37,7 +42,14 @@ if (argv._.length <1 || argv.help ){ let output_file = argv._[1]? argv._[1] : `${info.videoDetails.title}.cue` - let timestampsOnly = argv['timestamps-only']? argv['timestamps-only'] : false; + let forceTimestamps = argv['timestamps-only']? argv['timestamps-only'] : false; + + let forceDurations = argv['timestamps-are-durations']? argv['timestamps-are-durations'] : false; + + if (forceTimestamps && forceDurations) { + console.error("You can't pass both --timestamps-only and timestamps-are-durations"); + exit(1) + } let res = getArtistTitle(info.videoDetails.title,{ defaultArtist: "Unknown Artist", @@ -45,7 +57,7 @@ if (argv._.length <1 || argv.help ){ }); let [artist, album] = res artist = (info.videoDetails.media ? info.videoDetails.media.artist : artist) - let tracks = parse(info.videoDetails.description, {artist,timestampsOnly}) + 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/parser.js b/src/parser.js index 36609b3..77afabe 100644 --- a/src/parser.js +++ b/src/parser.js @@ -138,7 +138,7 @@ var fixDurations = function(list) { 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 }; - list[i].start.calc = 0 + list[i].start.calc = 0; } else { // All the others tracks start at the end of the previous one // And end at start time + duration @@ -152,7 +152,10 @@ var fixDurations = function(list) { } }; -export function parse(text, options = { artist: "Unknown", timestampsOnly : false }) { +export function parse( + text, + options = { artist: "Unknown", forceTimestamps: false, forceDurations: false } +) { _options = options; let durations = false; let result = text @@ -161,7 +164,7 @@ export function parse(text, options = { artist: "Unknown", timestampsOnly : fals .map(firstPass) .map(calcTimestamp); - if (options.timestampsOnly == false) { + if (!options.forceTimestamps) { // If our timestamps are not in increasing order // Assume that we've been given a duration list instead result.forEach((current, index, list) => { @@ -173,7 +176,7 @@ export function parse(text, options = { artist: "Unknown", timestampsOnly : fals } }); - if (durations) { + if (durations || options.forceDurations == true) { fixDurations(result); } } diff --git a/test/parser_test.js b/test/parser_test.js index 27fd294..2bfa933 100644 --- a/test/parser_test.js +++ b/test/parser_test.js @@ -117,7 +117,7 @@ describe("Parser", function() { let result = parse( `1. Artist - Title 5:00 2. Another Artist - Another Title 4:20`, - { timestampsOnly: true } + { forceTimestamps: true } ); assert.deepEqual(result[0].end, { ts: "00:4:20", @@ -135,6 +135,54 @@ describe("Parser", 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`, + { forceDurations: true } + ); + assert.deepEqual(result[0], { + track: 1, + _: { left_text: "Artist - Title", right_text: "" }, + title: "Title", + artist: "Artist", + end: { + ts: "00:01:00", + hh: 0, + mm: 1, + ss: 0, + calc: 60, + }, + start: { + ts: "00:00:00", + hh: 0, + mm: 0, + ss: 0, + calc: 0, + }, + }); + assert.deepEqual(result[1], { + track: 2, + _: { left_text: "Another Artist - Another Title", right_text: "" }, + title: "Another Title", + artist: "Another Artist", + start: { + ts: "00:01:00", + hh: 0, + mm: 1, + ss: 0, + calc: 60, + }, + end: { + ts: "00:02:15", + hh: 0, + mm: 2, + ss: 15, + calc: 135, + }, + }); + }); + it("should parse taylor swift", function() { let result = parse(`0:00 the 1 3:29 cardigan From a737d371057cf59eec67209b84dbf166ce0dd57f Mon Sep 17 00:00:00 2001 From: Nemo Date: Wed, 21 Jul 2021 13:50:48 +0530 Subject: [PATCH 6/9] [docs] Update README as per #72 --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bd50a81..fe114b5 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Generate CUE sheet from timestamps in youtube video description. 2. The video has timestamps in the video description. 3. The video is publicly available on Youtube. -`youtube-cue` will read the video description, get the timestamps and generate a [CUE sheet][cue] accordingly. +`youtube-cue` will read the video description, get the timestamps and generate a [CUE sheet][cue] accordingly. It will also work if track durations are used instead of timestamps. ## Anti-features @@ -42,6 +42,12 @@ You need to pass 2 parameters, a Youtube URL and a output CUE filename. YouTube where $VIDEOTITLE is the title of the YouTube video. + --timestamps-only Do not try to parse the timestamps as track durations + --timestamps-are-durations Parse timestamps as durations + + The above 2 are only needed to force behaviour in very specific edge cases, they should + not be required for most files. + Examples $ 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 @@ -66,7 +72,7 @@ function ytdl.album() { ## HACKING -- If this video does not work on a specific video, please attach the complete output +- If it does not work on a specific video, please attach the complete output - Pull Requests are welcome that add support for a better parser without breaking the existing tests - Please add tests for any new functionality From c4b1b4246126c4dae3a0251c1b6978c57456deba Mon Sep 17 00:00:00 2001 From: Nemo Date: Wed, 28 Jul 2021 17:22:05 +0530 Subject: [PATCH 7/9] feat: Parse as durations IFF first timestamp is >0 --- src/parser.js | 16 +++++++++------- test/parser_test.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/parser.js b/src/parser.js index 77afabe..5e4b701 100644 --- a/src/parser.js +++ b/src/parser.js @@ -167,14 +167,16 @@ export function parse( if (!options.forceTimestamps) { // 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; + if (result[0].start.calc!=0) { + result.forEach((current, index, list) => { + if (index > 0) { + let previous = list[index - 1]; + if (current.start.calc < previous.start.calc) { + durations = true; + } } - } - }); + }); + } if (durations || options.forceDurations == true) { fixDurations(result); diff --git a/test/parser_test.js b/test/parser_test.js index 2bfa933..bb3e592 100644 --- a/test/parser_test.js +++ b/test/parser_test.js @@ -113,6 +113,37 @@ describe("Parser", 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`); + assert.deepEqual(result[0], { + artist: "Artist", + title: "Title", + track: 1, + start: { ts: "00:00:00", hh: 0, mm: 0, ss: 0, calc: 0 }, + end: { ts: "00:01:00", hh: 0, mm: 1, ss: 0, calc: 60 }, + _: { left_text: "Artist - Title", right_text: "" }, + }); + + assert.deepEqual(result[1], { + artist: "Another Artist", + title: "Another Title", + track: 2, + end: { ts: "00:02:00", hh: 0, mm: 2, ss: 0, calc: 120 }, + start: { ts: "00:01:00", hh: 0, mm: 1, ss: 0, calc: 60 }, + _: { left_text: "Another Artist - Another Title", right_text: "" }, + }); + assert.deepEqual(result[2], { + artist: "Yet Another Artist", + title: "Yet another title", + 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: "" }, + }); + }); + it("should parse durations as timestamps when forced", function() { let result = parse( `1. Artist - Title 5:00 From 1aa0be59daa4a2b223abacdca51d3476e804befb Mon Sep 17 00:00:00 2001 From: Nemo Date: Wed, 28 Jul 2021 17:22:36 +0530 Subject: [PATCH 8/9] Change flags to --timestamps and --durations --- README.md | 17 ++++++++++------- index.js | 13 ++++++++----- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index fe114b5..b24b317 100644 --- a/README.md +++ b/README.md @@ -37,16 +37,19 @@ You need to pass 2 parameters, a Youtube URL and a output CUE filename. YouTube --help, Show help --audio-file, Input Audio File (optional) that is written to the CUE sheet - The default audio file is set to %VIDEOTITLE.m4a - The default output file is set to %VIDEOTITLE.cue + The default audio file is set to %VIDEOTITLE.m4a + The default output file is set to %VIDEOTITLE.cue - where $VIDEOTITLE is the title of the YouTube video. + where $VIDEOTITLE is the title of the YouTube video. - --timestamps-only Do not try to parse the timestamps as track durations - --timestamps-are-durations Parse timestamps as durations + Generally the parser detects whether numbers are positional timestamps or track durations. + To enforce a desired interpretation you can use these flags: - The above 2 are only needed to force behaviour in very specific edge cases, they should - not be required for most files. + --timestamps Parse as positional timestamps (relative to the start of the playlist) + --durations Parse as track durations + + The above 2 are only needed to force behaviour in + very specific edge cases, they should not be required for most files. Examples $ youtube-cue --audio-file audio.m4a "https://www.youtube.com/watch?v=THzUassmQwE" diff --git a/index.js b/index.js index 1aa9d3e..441756e 100755 --- a/index.js +++ b/index.js @@ -23,8 +23,11 @@ if (argv._.length <1 || argv.help ){ where $VIDEOTITLE is the title of the YouTube video. - --timestamps-only Do not try to parse the timestamps as track durations - --timestamps-are-durations Parse timestamps as durations + Generally the parser detects whether numbers are positional timestamps or track durations. + To enforce a desired interpretation you can use these flags: + + --timestamps Parse as positional timestamps (relative to the start of the playlist) + --durations Parse as track durations The above 2 are only needed to force behaviour in very specific edge cases, they should not be required for most files. @@ -42,12 +45,12 @@ if (argv._.length <1 || argv.help ){ let output_file = argv._[1]? argv._[1] : `${info.videoDetails.title}.cue` - let forceTimestamps = argv['timestamps-only']? argv['timestamps-only'] : false; + let forceTimestamps = argv['timestamps']? argv['timestamps'] : false; - let forceDurations = argv['timestamps-are-durations']? argv['timestamps-are-durations'] : false; + let forceDurations = argv['durations']? argv['durations'] : false; if (forceTimestamps && forceDurations) { - console.error("You can't pass both --timestamps-only and timestamps-are-durations"); + console.error("You can't pass both --timestamps and durations"); exit(1) } From 089c46d9784fb77d558c58c840080f7404ca6d88 Mon Sep 17 00:00:00 2001 From: Nemo Date: Wed, 28 Jul 2021 17:28:24 +0530 Subject: [PATCH 9/9] Prep for next release --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8d5a37..83118d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +### Added +- Tracklists using duration instead of timestamps are now supported. ## 1.0.5 - 2021-07-21 ### Changed diff --git a/package.json b/package.json index fcb6eb4..9156a36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "youtube-cue", - "version": "1.0.5", + "version": "1.0.6-beta.0", "description": "Generates Cue sheet from Youtube URL", "main": "index.js", "scripts": {