Skip to content

Commit

Permalink
Merge pull request #232 from phyzical/feature/229
Browse files Browse the repository at this point in the history
Add logic to support a templatable filename
  • Loading branch information
phyzical authored Jan 16, 2024
2 parents 782f659 + ad1e205 commit a925e5d
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 33 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules/
.vscode/
Makefile
.DS_STORE
*.mp3
1 change: 1 addition & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default {
outputOnly: false,
downloadLyrics: false,
searchFormat: '',
outputFormat: '{artistName}___{albumName}___{itemName}',
exclusionFilters: '',
},
spotifyApi: {
Expand Down
2 changes: 1 addition & 1 deletion lib/downloader.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ const downloader = async (youtubeLinks, output) => {
try {
await new Promise(doDownload);
downloadSuccess = true;
logSuccess('Download completed.');
logSuccess(`Download completed (${output}).`);
} catch (e) {
logInfo(e.message);
logInfo('Youtube error retrying download');
Expand Down
16 changes: 16 additions & 0 deletions lib/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,21 @@ export function cliInputs() {
'eg. $ spotifydl --sf "something {itemName} - {albumName} anyrandomextrastring"',
],
},
outputFormat: {
alias: 'of',
default: flagsConfig.outputFormat,
type: 'string',
helpText: [
'--output-format or --of',
'* allows for a user provided template to be used for the output filename',
'* supports the following contexts `albumName`, `artistName`,`itemName`',
'* note `itemName` references the search i.e track/show',
'* note the output argument is prepended to this argument',
'* note ___ is used to signify a directory',
'* if not provided will fallback to "{artistName}___{albumName}___{itemName}"',
'eg. $ spotifydl --of "some_extra_dir___{artistName}___{albumName}___{itemName}"',
],
},
exclusionFilters: {
alias: "ef",
default: flagsConfig.exclusionFilters,
Expand Down Expand Up @@ -355,5 +370,6 @@ export function cliInputs() {
username: inputFlags.username,
password: inputFlags.password,
downloadLyrics: inputFlags.downloadLyrics,
outputFormat: inputFlags.outputFormat,
};
}
26 changes: 26 additions & 0 deletions util/format-generators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Constants from './constants.js';

const {
YOUTUBE_SEARCH: { VALID_CONTEXTS },
} = Constants;

export function generateTemplateString(
itemName,
albumName,
artistName,
format,
) {
const contexts = format.match(/(?<=\{).+?(?=\})/g);
const invalidContexts = contexts.filter(
context => !VALID_CONTEXTS.includes(context),
);
if (invalidContexts.length > 0 || !contexts.length) {
throw new Error(`Invalid search contexts: ${invalidContexts}`);
}

contexts.forEach(
context => (format = format.replace(`{${context}}`, eval(context))),
);

return format;
}
19 changes: 7 additions & 12 deletions util/get-link.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import YoutubeSearch from 'yt-search';
import StringSimilarity from 'string-similarity';
import Constants from './constants.js';
import { logInfo } from './log-helper.js';
import { generateTemplateString } from './format-generators.js';

const {
YOUTUBE_SEARCH: { MAX_MINUTES, VALID_CONTEXTS },
YOUTUBE_SEARCH: { MAX_MINUTES },
INPUT_TYPES: { SONG },
} = Constants;
const search = promisify(YoutubeSearch);
Expand Down Expand Up @@ -66,19 +67,13 @@ const getLinks = async ({
}) => {
let links = [];
if (searchFormat.length) {
const contexts = searchFormat.match(/(?<=\{).+?(?=\})/g);
const invalidContexts = contexts.filter(
context => !VALID_CONTEXTS.includes(context),
links = await findLinks(
generateTemplateString(itemName, albumName, artistName, searchFormat),
type,
exclusionFilters,
);
if (invalidContexts.length > 0 || !contexts.length) {
throw new Error(`Invalid search contexts: ${invalidContexts}`);
}

contexts.forEach(context =>
searchFormat = searchFormat.replace(`{${context}}`, eval(context)),
);
links = await findLinks(searchFormat, type, exclusionFilters);
}
// custom search format failed or was never provided try the generic way
if (!links.length) {
const similarity = StringSimilarity.compareTwoStrings(itemName, albumName);
// to avoid duplicate song downloads
Expand Down
38 changes: 18 additions & 20 deletions util/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from './get-songdata.js';
import { logSuccess, logInfo, logFailure } from './log-helper.js';
import downloadSubtitles from '../lib/subtitle-downloader.js';
import { generateTemplateString } from './format-generators.js';

const {
INPUT_TYPES,
Expand All @@ -28,15 +29,18 @@ const {
downloadLyrics,
searchFormat,
exclusionFilters,
outputFormat,
} = cliInputs();

const itemOutputDir = item => {
const outputDir = path.normalize(output);
return outputOnly ? outputDir : path.join(
outputDir,
cleanOutputPath(item.artists[0]),
cleanOutputPath(item.album_name),
);
const itemOutputPath = (itemName, albumName, artistName) => {
itemName = cleanOutputPath(itemName || '_');
const generatedPathSegments = cleanOutputPath(
generateTemplateString(itemName, albumName, artistName, outputFormat),
).split('___');
return `${path.join(
path.normalize(output),
...(outputOnly ? [itemName] : generatedPathSegments),
)}.mp3`;
};

const downloadList = async list => {
Expand All @@ -47,13 +51,15 @@ const downloadList = async list => {
let currentCount = 0;
for (const nextItem of list.items) {
currentCount++;
const itemDir = itemOutputDir(nextItem);
const cached = findId(nextItem.id, itemOutputDir(nextItem));
if (!cached) {
const itemId = nextItem.id;
const itemName = nextItem.name;
const albumName = nextItem.album_name;
const artistName = nextItem.artists[0];
const fullItemPath = itemOutputPath(itemName, albumName, artistName);
const itemDir = fullItemPath.substr(0, fullItemPath.lastIndexOf('/'));
const cached = findId(nextItem.id, itemDir);

if (!cached) {
logInfo(
[
`${currentCount}/${totalItems}`,
Expand All @@ -62,8 +68,6 @@ const downloadList = async list => {
`Item: ${itemName}`,
].join('\n'),
);
const fileNameCleaned = cleanOutputPath(itemName) || '_';

//create the dir if it doesn't exist
fs.mkdirSync(itemDir, { recursive: true });

Expand All @@ -83,15 +87,9 @@ const downloadList = async list => {
},
);

const outputFilePath = path.resolve(
itemDir,
`${fileNameCleaned}.mp3`,
);
const outputFilePath = path.resolve(fullItemPath);

const downloadSuccessful = await downloader(
ytLinks,
outputFilePath,
);
const downloadSuccessful = await downloader(ytLinks, outputFilePath);

if (downloadSuccessful) {
await mergeMetadata(outputFilePath, nextItem);
Expand Down

0 comments on commit a925e5d

Please sign in to comment.