Initial work on file processor
This adds a new file processor mode. So the command changes from: youtube-cue https://youtu.be/watch?v=dfjosufoadsi to additionally supporting: youtube-cue description.nfo This however results in some problems. It is hard to get album and album artist information from a plain text file, so we instead now have 2 additional flags: --cue-performer "Performer for the entire track collection" --cue-title "Title for the entire collection" They usually, (but not always) will correspond to the album artist and album title. The changes are fairly obvious - I've also tried to make this more modular, so hopefully this is easier to use as a module.
This commit is contained in:
parent
666e802cfe
commit
085913ad69
81
index.js
81
index.js
|
@ -1,81 +1,70 @@
|
|||
#!/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 updateNotifier from "update-notifier";
|
||||
import pkg from "./src/package.js";
|
||||
import ytdl from 'ytdl-core';
|
||||
import getArtistTitle from 'get-artist-title';
|
||||
import { generate } from './src/cue.js';
|
||||
import minimist from 'minimist';
|
||||
import exit from 'process';
|
||||
import updateNotifier from 'update-notifier';
|
||||
import pkg from './src/package.js';
|
||||
import { processFile, processYoutube } from './src/process.js';
|
||||
|
||||
updateNotifier({ pkg }).notify();
|
||||
|
||||
let argv = minimist(process.argv.slice(2), {
|
||||
string: "audio-file",
|
||||
string: ['audio-file', 'cue-title', 'cue-performer'],
|
||||
});
|
||||
|
||||
if (argv.version) {
|
||||
console.log(pkg.version);
|
||||
} else if (argv._.length < 1 || argv.help) {
|
||||
console.log(`Usage
|
||||
$ youtube-cue [--audio-file audio.m4a] <youtube_url> [output_file]
|
||||
$ youtube-cue [--audio-file audio.m4a] --cue-title "Album Name" --cue-performer "Album Artist" <youtube_url|input_file> [output_file]
|
||||
|
||||
youtube_url: Pass a Youtube URL from where description is fetched and parsed
|
||||
input_file: Pass a plaintext file which contains the description text containing a timesheet.
|
||||
|
||||
Options
|
||||
--help, Show help
|
||||
--audio-file, Input Audio File (optional) that is written to the CUE sheet
|
||||
--audio-file, Input Audio File (optional) that is written to the CUE sheet.
|
||||
|
||||
If a youtube URL is passed, then
|
||||
The default audio file is set to %VIDEOTITLE.m4a
|
||||
The default output file is set to %VIDEOTITLE.cue
|
||||
Since video title is not available while parsing text files,
|
||||
The default audio file is set to audio.m4a
|
||||
The default output file is set to output.cue
|
||||
|
||||
where $VIDEOTITLE is the title of the YouTube video.
|
||||
|
||||
Generally the parser detects whether numbers are positional timestamps or track durations.
|
||||
Generally the parser detects whether numbers (such as 00:12) 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
|
||||
--durations Parse numbers as track durations instead.
|
||||
|
||||
The above 2 are only needed to force behaviour in very specific edge cases, they should
|
||||
not be required for most files.
|
||||
|
||||
--cue-title "Title that goes into the CUE file for the complete CUE sheet"
|
||||
--cue-performer "Performer for the entire collection. Commonly the album artist."
|
||||
|
||||
cue-title and cue-performer are recommended especially if you are reading the data from a text file.
|
||||
|
||||
--version Print version
|
||||
|
||||
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
|
||||
$ youtube-cue "https://youtu.be/THzUassmQwE" folklore.cue
|
||||
folklore.cue saved`);
|
||||
folklore.cue saved
|
||||
$ youtube-cue --audio-file audio.m4a --cue-performer "Magic Riders" description.txt
|
||||
"output.cue" saved`);
|
||||
} else {
|
||||
let url = argv._[0];
|
||||
let urlOrFile = argv._[0];
|
||||
|
||||
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 forceTimestamps = argv["timestamps"] ? argv["timestamps"] : false;
|
||||
|
||||
let forceDurations = argv["durations"] ? argv["durations"] : false;
|
||||
|
||||
if (forceTimestamps && forceDurations) {
|
||||
console.error("You can't pass both --timestamps and durations");
|
||||
exit(1);
|
||||
if (fs.existsSync(urlOrFile)) {
|
||||
let r = processFile(urlOrFile, argv);
|
||||
} else {
|
||||
let r = processYoutube(urlOrFile, argv);
|
||||
}
|
||||
|
||||
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,
|
||||
forceTimestamps,
|
||||
forceDurations,
|
||||
});
|
||||
generate({ tracks, artist, audioFile, album }, output_file);
|
||||
console.log(`"${output_file}" saved`);
|
||||
});
|
||||
generate({ r.tracks, r.artist, r.audioFile, r.album }, r.outputFile);
|
||||
console.log(`"${outputFile}" saved`);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "youtube-cue",
|
||||
"version": "1.0.6",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
import fs from 'fs';
|
||||
import { parse } from './parser.js';
|
||||
|
||||
const DEFAULT_AUDIO_FILE = 'audio.m4a';
|
||||
const DEFAULT_ARTIST = 'Unknown Artist';
|
||||
const DEFAULT_ALBUM = 'Unknown Album';
|
||||
const DEFAULT_OUTPUT_FILE = 'output.cue';
|
||||
|
||||
function validateArgs(argv) {
|
||||
if (forceTimestamps(argv) && forceDurations(argv)) {
|
||||
console.error("You can't pass both --timestamps and durations");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function forceTimestamps(argv) {
|
||||
return argv['timestamps'] ? argv['timestamps'] : false;
|
||||
}
|
||||
|
||||
function forceDurations(argv) {
|
||||
argv['durations'] ? argv['durations'] : false;
|
||||
}
|
||||
|
||||
export function processFile(inputFile, argv) {
|
||||
validateArgs(argv);
|
||||
let audioFile = argv['audio-file'] ? argv['audio-file'] : `audio.m4a`;
|
||||
let contents = fs.readFileSync(inputFile, 'utf8');
|
||||
let tracks = parse(contents, {
|
||||
artist: DEFAULT_ARTIST,
|
||||
forceTimestamps: forceTimestamps(argv),
|
||||
forceDurations: forceDurations(argv),
|
||||
});
|
||||
let artist = argv['cue-performer'] ? argv['cue-performer'] : DEFAULT_ARTIST;
|
||||
let album = argv['cue-title'] ? argv['cue-title'] : DEFAULT_ALBUM;
|
||||
return {
|
||||
tracks: tracks,
|
||||
artist: artist,
|
||||
audioFile: audioFile,
|
||||
album: album,
|
||||
outputFile: argv._[1] ? argv._[1] : DEFAULT_OUTPUT_FILE,
|
||||
};
|
||||
}
|
||||
|
||||
export async function processYoutube(url, argv) {
|
||||
let info = await ytdl.getInfo(url);
|
||||
let audioFile = argv['audio-file']
|
||||
? argv['audio-file']
|
||||
: `${info.videoDetails.title}.m4a`;
|
||||
let outputFile = argv._[1] ? argv._[1] : `${info.videoDetails.title}.cue`;
|
||||
|
||||
let res = getArtistTitle(info.videoDetails.title, {
|
||||
defaultArtist: DEFAULT_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,
|
||||
forceTimestamps,
|
||||
forceDurations,
|
||||
});
|
||||
return {
|
||||
tracks: tracks,
|
||||
artist: artist,
|
||||
audioFile: audioFile,
|
||||
album: album,
|
||||
outputFile: argv._[1] ? argv._[1] : DEFAULT_OUTPUT_FILE,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*jshint esversion: 6 */
|
||||
import { strict as assert } from 'assert';
|
||||
import minimist from 'minimist';
|
||||
import { processFile } from '../src/process.js';
|
||||
|
||||
describe('FileProcessor', function() {
|
||||
it('should process simple files correctly', function() {
|
||||
let r = processFile('test/sample.txt', minimist([]));
|
||||
assert.equal(15, r.tracks.length);
|
||||
assert.equal(r.artist, 'Unknown Artist');
|
||||
assert.equal(r.audioFile, 'audio.m4a');
|
||||
assert.equal(r.album, 'Unknown Album');
|
||||
assert.equal(r.outputFile, 'output.cue');
|
||||
});
|
||||
|
||||
it('should process files with arguments correctly', function() {
|
||||
let args = minimist([
|
||||
'--audio-file',
|
||||
'demo.mp3',
|
||||
'--cue-performer',
|
||||
'Doors',
|
||||
'--cue-title',
|
||||
'Windows',
|
||||
]);
|
||||
let r = processFile('test/sample.txt', args);
|
||||
assert.equal(15, r.tracks.length);
|
||||
assert.equal(r.artist, 'Doors');
|
||||
assert.equal(r.audioFile, 'demo.mp3');
|
||||
assert.equal(r.album, 'Windows');
|
||||
assert.equal(r.outputFile, 'output.cue');
|
||||
});
|
||||
|
||||
it('should parse output files correctly', function() {
|
||||
let args = minimist(['test/sample2.txt', 'test/o.cue']);
|
||||
let r = processFile('test/sample.txt', args);
|
||||
assert.equal(r.outputFile, 'test/o.cue');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
00:40 The Coders - Hello World
|
||||
1:00 This is not the end
|
||||
Something else in the middle
|
||||
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
|
||||
05. Outre Lux 19:07 - 23:11
|
||||
06. Balsam Massacre 23:11 - 26:24
|
||||
07. Eco Friend 26:24 - 32:15
|
||||
08. Off-Piste 32:15 - 36:53
|
||||
09. Aura 36:53 - 41:44
|
||||
10. Bombogenesis 41:44 - 48:20
|
||||
Hello World 48:20
|
||||
50:23 Bye World
|
Loading…
Reference in New Issue