Skip to content

Try add MusicBrainz tag to file #82

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 43 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
dabfef3
Try add MusicBrainz tag to file
Maxmystere Sep 25, 2021
4b12c70
Move logic to cli:trackQueue
Maxmystere Oct 1, 2021
12aec89
Clean code
Maxmystere Oct 1, 2021
e4aff77
Final code cleaning and separation
Maxmystere Oct 1, 2021
b55eb42
Cleaner logs
Maxmystere Oct 1, 2021
6ccc4b1
Sync logs
Maxmystere Oct 1, 2021
c95d255
Merge branch 'master' into Add-MBID-tag
miraclx Oct 2, 2021
fc22292
touch lookup logic, extract extra metadata
miraclx Oct 2, 2021
d524f78
drop AtomicParsley ignore
miraclx Oct 2, 2021
524774a
Merge branch 'master' into Add-MBID-tag
miraclx Oct 3, 2021
cdbbac9
show error msg + code when musicbrainz lookup failed
miraclx Oct 3, 2021
2fe8a68
add cli flag `-m, --musicbrainz` for enabling musicbrainz functionality
miraclx Oct 3, 2021
afc16e6
default storefront = us;
miraclx Oct 3, 2021
2248ca1
embed release country
miraclx Oct 3, 2021
4d0ba9f
apply default if not using musicbrainz
miraclx Oct 3, 2021
0757056
catch and diffuse any caught error in the current execution loop
miraclx Oct 3, 2021
9c1129c
prioritize storefront+digital media matches
miraclx Oct 3, 2021
7825c3c
cache musicbrainz lookups for efficiency
miraclx Oct 3, 2021
e1a16e3
limit the scope of the args fields: inc & json only
miraclx Oct 3, 2021
2d5c59b
remove static inc from template url
miraclx Oct 3, 2021
c370653
releaseType should be lowerCase
miraclx Oct 3, 2021
52a948c
Merge branch 'miraclx:master' into Add-MBID-tag
Maxmystere May 14, 2022
e489991
Merge remote-tracking branch 'origin/master' into Add-MBID-tag
Maxmystere Aug 23, 2022
320a206
Fix merge issue
Maxmystere Aug 23, 2022
e1ecc53
Finish merge + fixes
Maxmystere Aug 23, 2022
4b79bb8
Add flac support + use taglib for tagging flac
Maxmystere Aug 25, 2022
a454a83
CRLF -> LF
Maxmystere Aug 25, 2022
b49a8e9
Disable Musicbrainz for AtomicParsley has it is unsupported
Maxmystere Aug 25, 2022
8042571
Comment fix
Maxmystere Aug 25, 2022
cfac169
Merge branch 'master' into Add-MBID-tag
Maxmystere Aug 25, 2022
bec59fb
Fix format
Maxmystere Aug 29, 2022
c8a048b
Merge branch 'master' into Add-MBID-tag
Maxmystere Aug 29, 2022
8ecc465
Secure get
Maxmystere Aug 30, 2022
a1b5fd3
eslint
Maxmystere Aug 30, 2022
36b16e1
Up from master
Maxmystere Sep 20, 2022
86fc599
Merge remote-tracking branch 'origin/master' into Add-MBID-tag
Maxmystere Sep 20, 2022
b84283f
LF
Maxmystere Sep 20, 2022
7d6796e
Fix issue with metadata extract
Maxmystere Sep 20, 2022
d50b7e6
Merge remote-tracking branch 'origin/master' into Add-MBID-tag
Maxmystere Oct 28, 2022
0c3f74d
Increase security
Maxmystere Oct 28, 2022
c3633d2
Merge remote-tracking branch 'origin/master' into Add-MBID-tag
Maxmystere Dec 18, 2022
4e67396
Merge remote-tracking branch 'origin/master' into Add-MBID-tag
Maxmystere Jan 1, 2023
1185531
Merge remote-tracking branch 'origin/master' into Add-MBID-tag
Maxmystere Apr 9, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 41 additions & 12 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const FreyrCore = require('./src/freyr');
const AuthServer = require('./src/cli_server');
const AsyncQueue = require('./src/async_queue');
const parseRange = require('./src/parse_range');
const musicBrainz = require('./src/musicbrainz.js');
const StackLogger = require('./src/stack_logger');
const packageJson = require('./package.json');
const streamUtils = require('./src/stream_utils');
Expand All @@ -43,7 +44,13 @@ function parseMeta(params) {
return Object.entries(params || {})
.filter(([, value]) => ![undefined, null].includes(value))
.map(([key, value]) =>
Array.isArray(value) ? value.map(tx => (tx ? [`--${key}`, ...(Array.isArray(tx) ? tx : [tx])] : '')) : [`--${key}`, value],
Array.isArray(value)
? value
.filter(val => val !== undefined)
.map((tx, args) =>
(args = Array.isArray(tx) ? tx : [tx]).every(val => val !== undefined) ? [`--${key}`, ...args] : [],
)
: [`--${key}`, value],
)
.flat(Infinity);
}
Expand Down Expand Up @@ -445,6 +452,7 @@ async function init(queries, options) {
netCheck: true,
attemptAuth: true,
autoOpenBrowser: true,
musicBrainz: false,
},
dirs: {
output: '.',
Expand Down Expand Up @@ -508,6 +516,7 @@ async function init(queries, options) {
netCheck: options.netCheck,
attemptAuth: options.auth,
autoOpenBrowser: options.browser,
musicBrainz: options.musicbrainz,
});
Config.playlist = lodash.merge(Config.playlist, {
always: !!options.playlist,
Expand Down Expand Up @@ -847,10 +856,19 @@ async function init(queries, options) {
gapless: options.gapless, // pgap
rDNSatom: [
// ----
['Digital Media', 'name=MEDIA', 'domain=com.apple.iTunes'],
[track.isrc, 'name=ISRC', 'domain=com.apple.iTunes'],
[track.artists[0], 'name=ARTISTS', 'domain=com.apple.iTunes'],
[track.label, 'name=LABEL', 'domain=com.apple.iTunes'],
[track.musicBrainz.trackId, 'name="MusicBrainz Track Id"', 'domain=com.apple.iTunes'],
[track.musicBrainz.artistId, 'name="MusicBrainz Artist Id"', 'domain=com.apple.iTunes'],
[track.musicBrainz.artistId, 'name="MusicBrainz Album Artist Id"', 'domain=com.apple.iTunes'],
[track.musicBrainz.releaseId, 'name="MusicBrainz Album Id"', 'domain=com.apple.iTunes'],
[track.musicBrainz.releaseGroupId, 'name="MusicBrainz Release Group Id"', 'domain=com.apple.iTunes'],
[track.musicBrainz.barcode, 'name=BARCODE', 'domain=com.apple.iTunes'],
[track.musicBrainz.releaseStatus, 'name="MusicBrainz Album Status"', 'domain=com.apple.iTunes'],
[track.musicBrainz.releaseCountry, 'name="MusicBrainz Album Release Country"', 'domain=com.apple.iTunes'],
[track.musicBrainz.script, 'name=SCRIPT', 'domain=com.apple.iTunes'],
[track.musicBrainz.media, 'name=MEDIA', 'domain=com.apple.iTunes'],
[`${meta.service[symbols.meta].DESC}: ${track.uri}`, 'name=SOURCE', 'domain=com.apple.iTunes'],
[
`${audioSource.service[symbols.meta].DESC}: ${audioSource.source.videoId}`,
Expand All @@ -875,12 +893,12 @@ async function init(queries, options) {
encodingTool: `freyr-js cli v${packageJson.version}`, // ©too
encodedBy: 'd3vc0dr', // ©enc
artwork: files.image.file.name, // covr
// sortOrder: [
// ['name', 'NAME'], // sonm
// ['album', 'NAME'], // soal
// ['artist', 'NAME'], // soar
// ['albumartist', 'NAME'], // soaa
// ],
sortOrder: [
// ['name', 'NAME'], // sonm
// ['album', 'NAME'], // soal
['artist', track.musicBrainz.artistSortOrder], // soar
['albumartist', track.musicBrainz.artistSortOrder], // soaa
],
})
.finally(() => files.image.file.removeCallback())
.catch(err => Promise.reject({err, code: 8}));
Expand Down Expand Up @@ -982,6 +1000,12 @@ async function init(queries, options) {
}
trackLogger.log('| [\u2022] Track exists. Overwriting...');
}
track.musicBrainz =
(await processPromise(props.extraTrackMeta, trackLogger, {
onInit: '| \u27a4 Sourcing extra metadata...',
noVal: () => '[skipped]\n',
onErr: err => `[failed, ${err.statusCode ? `(${err.statusCode}) ` : ''}${err.message}]\n`,
})) || {};
trackLogger.log('| \u27a4 Collating sources...');
const audioSource = await props.collectSources((service, sourcesPromise) =>
processPromise(sourcesPromise, trackLogger, {
Expand Down Expand Up @@ -1038,11 +1062,15 @@ async function init(queries, options) {
const outFilePath = xpath.join(outFileDir, outFileName);
const fileExists = fs.existsSync(outFilePath);
const processTrack = !fileExists || options.force;
let collectSources;
if (processTrack) collectSources = buildSourceCollectorFor(track, results => results[0]);
let collectSources, extraTrackMeta;
if (processTrack) {
collectSources = buildSourceCollectorFor(track, results => results[0]);
if (Config.opts.musicBrainz && track.isrc)
extraTrackMeta = musicBrainz.lookupISRC(track.isrc, options.storefront || 'us');
}
const meta = {trackName, outFileDir, outFilePath, track, service};
return trackQueue
.push({track, meta, props: {collectSources, fileExists, processTrack, logger}})
.push({track, meta, props: {collectSources, extraTrackMeta, fileExists, processTrack, logger}})
.then(trackObject => ({...trackObject, meta}))
.catch(errObject => ({meta, code: 10, ...errObject}));
},
Expand Down Expand Up @@ -1395,6 +1423,7 @@ program
['specify a preferred download source or a `,`-separated preference order', `(valid: ${VALIDS.downloaders})`].join('\n'),
'yt_music',
)
.option('-m, --musicbrainz', 'attempt to source and embed extra metadata from MusicBrainz')
.option(
'-l, --filter <MATCH>',
[
Expand Down Expand Up @@ -1445,7 +1474,7 @@ program
)
.option('--via-tor', 'tunnel network traffic through the tor network (unimplemented)')
.option('--cache-dir <DIR>', 'specify alternative cache directory, `<tmp>` for tempdir')
.option('-m, --mem-cache <SIZE>', 'max size of bytes to be cached in-memory for each download chunk')
.option('-M, --mem-cache <SIZE>', 'max size of bytes to be cached in-memory for each download chunk')
.option('--no-mem-cache', 'disable in-memory chunk caching (restricts to sequential download)')
.option('--timeout <N>', 'network inactivity timeout (ms)', 10000)
.option('--no-auth', 'skip authentication procedure')
Expand Down
3 changes: 2 additions & 1 deletion conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"opts": {
"netCheck": true,
"attemptAuth": true,
"autoOpenBrowser": true
"autoOpenBrowser": true,
"musicBrainz": false
},
"filters": [],
"dirs": {
Expand Down
81 changes: 63 additions & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"tmp": "^0.2.1",
"tough-cookie": "^4.0.0",
"xbytes": "^1.6.2",
"xml2js": "^0.4.23",
"xprogress": "^0.17.3",
"youtube-dl-exec": "^1.2.6",
"yt-search": "^2.5.1"
Expand Down
58 changes: 58 additions & 0 deletions src/musicbrainz.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const got = require('got');
const {parseStringPromise: xml2js} = require('xml2js');

class MusicBrainzError extends Error {
constructor(message, statusCode) {
super(message);
if (statusCode) this.statusCode = statusCode;
}
}

async function query(entity_type, entity, args) {
let response = await got(`https://musicbrainz.org/ws/2/${entity_type}/${entity}?inc=artists+releases+discids`, {
searchParams: {...args, ...('inc' in args ? {inc: args.inc.join('+')} : {}), ...(args.json ? {fmt: 'json'} : {})},
});
let body;
try {
body = response.body.startsWith('<?xml')
? await xml2js(response.body, {trim: true, mergeAttrs: true, explicitRoot: false, explicitArray: false})
: JSON.parse(response.body);
} catch {
throw new MusicBrainzError('Invalid Server Response');
}
if (response.statusCode !== 200) {
throw new MusicBrainzError(body.error || 'An error occurred', response.statusCode);
}
return body;
}

async function lookupISRC(isrc, storefront) {
let {
recording: {
id: trackId,
'release-list': {release: releases},
},
} = (await query('isrc', isrc, {inc: ['releases']})).isrc['recording-list'];
releases = Array.isArray(releases) ? releases : [releases];

let {id: releaseId} = releases.find(release => release.country.toLowerCase() === storefront) || releases[0];

let release = await query('release', releaseId, {inc: ['artists', 'release-groups', 'media'], json: true});

let {artist: artistMeta} = release['artist-credit'][0];
return {
trackId,
releaseId,
artistId: artistMeta.id,
artistSortOrder: artistMeta['sort-name'],
releaseGroupId: release['release-group'].id,
releaseType: release['release-group']['primary-type'],
releaseCountry: release.country,
releaseStatus: release.status.toLowerCase(),
media: release.media[0].format,
script: release['text-representation'].script,
barcode: release.barcode,
};
}

module.exports = {lookupISRC};