Skip to content

Commit 68b6aad

Browse files
authored
Merge pull request #3 from adamjakab/devel
Devel
2 parents 061a86e + 6434401 commit 68b6aad

8 files changed

Lines changed: 157 additions & 114 deletions

File tree

BEETSDIR/config.yaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,17 @@ xtractor:
2828
force: no
2929
quiet: no
3030
items_per_run: 0
31-
keep_output: no
31+
keep_output: yes
3232
keep_profiles: no
3333
output_path: /Users/jackisback/Documents/Projects/Python/BeetsPluginXtractor/BEETSDIR/xtraction
3434
low_level_extractor: /Users/jackisback/Documents/Projects/Other/extractors/beta5/essentia_streaming_extractor_music
3535
high_level_extractor: /Users/jackisback/Documents/Projects/Other/extractors/beta5/essentia_streaming_extractor_music_svm
3636
low_level_profile:
37-
outputFormat: yaml
38-
outputFrames: 0
37+
outputFormat: yaml
38+
outputFrames: 0
3939
high_level_profile:
40-
outputFormat: json
41-
highlevel:
40+
outputFormat: json
41+
highlevel:
4242
compute: 1
4343
svm_models:
4444
- /Users/jackisback/Documents/Projects/Other/extractors/svm_models_beta5/danceability.history
@@ -52,6 +52,6 @@ xtractor:
5252
- /Users/jackisback/Documents/Projects/Other/extractors/svm_models_beta5/mood_relaxed.history
5353
- /Users/jackisback/Documents/Projects/Other/extractors/svm_models_beta5/mood_sad.history
5454
- /Users/jackisback/Documents/Projects/Other/extractors/svm_models_beta5/voice_instrumental.history
55-
chromaprint:
56-
compute: 0
55+
chromaprint:
56+
compute: 0
5757

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
prune test
22
include LICENSE.txt
33
include README.md
4+
include beetsplug/xtractor/version.py
5+
include beetsplug/xtractor/config_default.yml

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,21 @@
55

66
# Xtractor (beets plugin)
77

8-
*A [beets](https://github.com/beetbox/beets) plugin for insane obsessive-compulsive music geeks.*
8+
The *beets-xtractor* plugin lets you use the extractors of the [Essentia](https://essentia.upf.edu/index.html) project developed by the Music Technology Group.
99

10-
The *beets-xtractor* plugin lets you use the extractors of the Essentia project developed by (credits here) to...
10+
11+
*NOTE: This plugin is highly unstable and not at all documented! Use it at your own risk*
1112

1213

1314
## Installation
1415
The plugin can be installed via:
1516

1617
```shell script
17-
$ pip install beets-xtractor (*not just yet!*)
18+
$ pip install beets-xtractor
1819
```
1920

2021

22+
## References
2123
[Essentia](https://essentia.upf.edu/index.html)
2224

2325
[SVM Models](https://essentia.upf.edu/svm_models/)
@@ -28,4 +30,3 @@ $ pip install beets-xtractor (*not just yet!*)
2830

2931
[Acousticbrainz Downloads](https://acousticbrainz.org/download)
3032

31-

beetsplug/xtractor/__init__.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,22 @@
44
# Created: 3/13/20, 12:17 AM
55
# License: See LICENSE.txt
66

7+
import os
8+
79
from beets.plugins import BeetsPlugin
8-
from beets.util import cpu_count
10+
from beets.util.confit import ConfigSource, load_yaml
911

1012
from beetsplug.xtractor.command import XtractorCommand
1113

1214

1315
class XtractorPlugin(BeetsPlugin):
16+
_default_plugin_config_file_name_ = 'config_default.yml'
17+
1418
def __init__(self):
1519
super(XtractorPlugin, self).__init__()
16-
self.config.add({
17-
'auto': False,
18-
'dry-run': False,
19-
'write': True,
20-
'threads': cpu_count(),
21-
'force': False,
22-
'quiet': False
23-
})
20+
config_file_path = os.path.join(os.path.dirname(__file__), self._default_plugin_config_file_name_)
21+
source = ConfigSource(load_yaml(config_file_path) or {}, config_file_path)
22+
self.config.add(source)
2423

2524
def commands(self):
2625
return [XtractorCommand(self.config)]

beetsplug/xtractor/command.py

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,32 @@ def xtract(self):
142142
# Set up the query for unprocessed items
143143
unprocessed_items_query = dbcore.query.OrQuery(
144144
[
145+
# LOW
146+
# dbcore.query.NoneQuery(u'average_loudness', fast=False),
147+
dbcore.query.MatchQuery(u'average_loudness', None, fast=False),
145148
dbcore.query.NumericQuery(u'bpm', u'0'),
146-
dbcore.query.MatchQuery(u'gender', u'', fast=False),
149+
dbcore.query.MatchQuery(u'danceability', None, fast=False),
150+
dbcore.query.MatchQuery(u'beats_count', None, fast=False),
151+
152+
# HIGH
153+
dbcore.query.MatchQuery(u'danceable', None, fast=False),
147154
dbcore.query.MatchQuery(u'gender', None, fast=False),
155+
dbcore.query.MatchQuery(u'genre_rosamerica', None, fast=False),
156+
dbcore.query.MatchQuery(u'voice_instrumental', None, fast=False),
157+
158+
dbcore.query.MatchQuery(u'mood_acoustic', None, fast=False),
159+
dbcore.query.MatchQuery(u'mood_aggressive', None, fast=False),
160+
dbcore.query.MatchQuery(u'mood_electronic', None, fast=False),
161+
dbcore.query.MatchQuery(u'mood_happy', None, fast=False),
162+
dbcore.query.MatchQuery(u'mood_party', None, fast=False),
163+
dbcore.query.MatchQuery(u'mood_relaxed', None, fast=False),
164+
dbcore.query.MatchQuery(u'mood_sad', None, fast=False),
148165
]
149166
)
150167
combined_query = dbcore.query.AndQuery([parsed_query, unprocessed_items_query])
151168

169+
log.debug("Combined query: {}".format(combined_query))
170+
152171
# Get the library items
153172
library_items = self.lib.items(combined_query, parsed_sort)
154173
if len(library_items) == 0:
@@ -213,23 +232,22 @@ def _run_analysis_high_level(self, item):
213232
self._say("Running high-level analysis: {0}".format(input_path))
214233
self._run_essentia_extractor(extractor_path, input_path, output_path, profile_path)
215234

216-
# todo: allow failing individual attributes
217235
try:
218-
audiodata = bpmHelper.extract_high_level_data(output_path)
236+
target_map = self.config["high_level_targets"]
237+
audiodata = bpmHelper.extract_from_output(output_path, target_map)
219238
except FileNotFoundError as e:
220239
self._say("File not found: {0}".format(e))
221240
return
222241
except KeyError as e:
223242
self._say("Attribute not present: {0}".format(e))
224243
return
225244

245+
print(audiodata)
246+
226247
if not self.cfg_dry_run:
227-
for attr in [
228-
"danceable", "gender", "genre_rosamerica", "voice_instrumental",
229-
"mood_acoustic", "mood_aggressive", "mood_electronic",
230-
"mood_happy", "mood_party", "mood_relaxed", "mood_sad"
231-
]:
232-
setattr(item, attr, audiodata.get(attr))
248+
for attr in audiodata.keys():
249+
if audiodata.get(attr):
250+
setattr(item, attr, audiodata.get(attr))
233251
item.store()
234252

235253
def _run_analysis_low_level(self, item):
@@ -252,7 +270,9 @@ def _run_analysis_low_level(self, item):
252270
self._run_essentia_extractor(extractor_path, input_path, output_path, profile_path)
253271

254272
try:
255-
audiodata = bpmHelper.extract_low_level_data(output_path)
273+
target_map = self.config["low_level_targets"]
274+
audiodata = bpmHelper.extract_from_output(output_path, target_map)
275+
256276
except FileNotFoundError as e:
257277
self._say("File not found: {0}".format(e))
258278
return
@@ -261,8 +281,9 @@ def _run_analysis_low_level(self, item):
261281
return
262282

263283
if not self.cfg_dry_run:
264-
for attr in ["bpm"]:
265-
setattr(item, attr, audiodata.get(attr))
284+
for attr in audiodata.keys():
285+
if audiodata.get(attr):
286+
setattr(item, attr, audiodata.get(attr))
266287
item.store()
267288

268289
def _run_essentia_extractor(self, extractor_path, input_path, output_path, profile_path):
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
auto: no
2+
dry-run: no
3+
write: yes
4+
threads: 1
5+
force: no
6+
quiet: no
7+
low_level_targets:
8+
average_loudness:
9+
path: "lowlevel.average_loudness"
10+
type: float
11+
bpm:
12+
path: "rhythm.bpm"
13+
type: integer
14+
danceability:
15+
path: "rhythm.danceability"
16+
type: float
17+
beats_count:
18+
path: "rhythm.beats_count"
19+
type: integer
20+
high_level_targets:
21+
danceable:
22+
path: "highlevel.danceability.all.danceable"
23+
type: float
24+
gender:
25+
path: "highlevel.gender.value"
26+
type: string
27+
is_male:
28+
path: "highlevel.gender.all.male"
29+
type: float
30+
is_female:
31+
path: "highlevel.gender.all.female"
32+
type: float
33+
genre_rosamerica:
34+
path: "highlevel.genre_rosamerica.value"
35+
type: string
36+
voice_instrumental:
37+
path: "highlevel.voice_instrumental.value"
38+
type: string
39+
is_voice:
40+
path: "highlevel.voice_instrumental.all.voice"
41+
type: float
42+
is_instrumental:
43+
path: "highlevel.voice_instrumental.all.instrumental"
44+
type: float
45+
mood_acoustic:
46+
path: "highlevel.mood_acoustic.all.acoustic"
47+
type: float
48+
mood_aggressive:
49+
path: "highlevel.mood_aggressive.all.aggressive"
50+
type: float
51+
mood_electronic:
52+
path: "highlevel.mood_electronic.all.electronic"
53+
type: float
54+
mood_happy:
55+
path: "highlevel.mood_happy.all.happy"
56+
type: float
57+
mood_party:
58+
path: "highlevel.mood_party.all.party"
59+
type: float
60+
mood_relaxed:
61+
path: "highlevel.mood_relaxed.all.relaxed"
62+
type: float
63+
mood_sad:
64+
path: "highlevel.mood_sad.all.sad"
65+
type: float

0 commit comments

Comments
 (0)