Coder Social home page Coder Social logo

beetsplugingoingrunning's Introduction

Test & Release & Deploy Coverage Status PyPi PyPI pyversions MIT license

Going Running (Beets Plugin)

The beets-goingrunning is a beets plugin for obsessive-compulsive music geek runners. It lets you configure different training activities by filtering songs based on their tag attributes (bpm, length, mood, loudness, etc), generates a list of songs for that specific training and copies those songs to your player device.

Have you ever tried to beat your PR and have good old Bob singing about ganja in the background? It doesn’t really work. Or don't you know how those recovery session end up with the Crüe kickstarting your heart? You'll be up in your Zone 4 in no time.

The fact is that it is very difficult and time consuming to compile an appropriate playlist for a specific training session. This plugin tries to help runners with this by allowing them to use their own library.

Introduction

To use this plugin at its best and to benefit the most from your library, you will want to make sure that your songs have the most possible information on rhythm, moods, loudness, etc.

Without going into much detail the most fundamental information you will want to harvest is bpm. Normally, when you run a fast pace training you will keep your pace (the number of times your feet hit the ground in a minute) around 170-180. If you are listening to songs with the same rhythm it helps a lot. If your library has many songs without the bpm information (check with beet ls bpm:0) you will not be able to use those songs. So, you should consider updating them. There are many tools you can use:

  1. Use the built-in acousticbrainz plugin to fetch the bpm plus many other information about your songs. This is your starting point. It is as easy as beet cousticbrainz and it will do the rest. This tool is based on an on-line database so it will be able to fetch only what has been submitted by someone else. If you have many "uncommon" songs you will need to integrate it with other tools. (My library was still 30% uncovered after a full scan.)

  2. Use the bpmanalyser plugin. This will scan your songs and calculate the tempo (bpm) value for them. If you have a big collection it might take a while, but since this tool does not use an on-line database, you can potentially end up with 100% coverage. This plugin will only give you bpm info.

  3. Essentia extractors. The Acoustic Brainz (AB) project is based partly on these low and high level extractors. There is currently a highly under-development project xtractor plugin which aims to bring your library to 100% coverage. However, for the time being there are no distributable static extractors, so wou will have to compile your own extractors.

There are many other ways and tools we could list here but I think you got the point...

Installation

The plugin can be installed via:

$ pip install beets-goingrunning

Activate the plugin in your configuration file by adding goingrunning to the plugins section:

plugins:
  - goingrunning

Check if plugin is loaded with beet version. It should list 'goingrunning' amongst the loaded plugins.

If you already have the plugin installed but a newer version is available you can use pip install --upgrade beets-goingrunning to upgrade it.

Usage

Invoke the plugin as:

$ beet goingrunning training [options] [QUERY...]

or with the shorthand alias run:

$ beet run training [options] [QUERY...]

The following command line options are available:

--list [-l]: List all the configured trainings. With beet goingrunning --list you will be presented the list of the trainings you have configured in your configuration file.

--count [-c]: Count the number of songs available for a specific training. With beet goingrunning longrun --count you can see how many of your songs will fit the specifications for the longrun training.

--dry-run [-r]: Only display what would be done without actually making changes to the file system. The plugin will run without clearing the destination and without copying any files.

--quiet [-q]: Do not display any output from the command.

--version [-v]: Display the version number of the plugin. Useful when you need to report some issue and you have to state the version of the plugin you are using.

Configuration

All your configuration will need to be created under the key goingrunning. There are three concepts you need to know to configure the plugin: targets, trainings and flavours. They are explained in detail below.

Targets

Targets are named destinations on your file system to which you will be copying your songs. The targets key allows you to define multiple targets so that under a specific training session you will only need to refer to it with the target key.

The configuration of the target names MPD1 will look like this:

goingrunning:
  targets:
    MPD1:
      device_root: /media/MPD1/
      device_path: MUSIC/AUTO/
      clean_target: yes
      delete_from_device:
        - LIBRARY.DAT
      generate_playlist: yes
      copy_files: yes

The key device_root indicates where your operating system mounts the device. The key device_path indicates the folder inside the device to which your audio files will be copied. In the above example the final destination is /media/MPD1/MUSIC/AUTO/. It is assumed that the folder indicated in the device_path key exists. If it doesn't the plugin will exit with a warning. The device path can also be an empty string if you want to store the files in the root folder of the device.

The key clean_target, when set to yes, instructs the plugin to clean the device_path folder before copying the new songs to the device. This will remove all audio songs and playlists found in that folder.

Some devices might have library files or other data files which need to be deleted in order for the device to re-discover the new songs. These files can be added to the delete_from_device key. The files listed here are relative to the device_root directive.

You can also generate a playlist by setting the generate_playlist option to yes. It will create an .m3u playlist file and store it to your device_path location.

There might be some special conditions in which you do not want to copy files to the device. In fact, the destination folder (device_root/device_path) might refer to an ordinary folder on your computer and you might want to create only a playlist there. In this case, you want to disable the copying of the music files by setting copy_files: no. By default, copy_files is always enabled so in the above MPD1 target it could also be omitted and files would be copied all the same.

Trainings

Trainings are the central concept behind the plugin. When you are "going running" you will already have in mind the type of training you will be doing. This configuration section allows you to preconfigure filters that will allow you to launch a beet run 10K command whilst you are tying your shoelaces and be out of the house as quick as possible. In fact, the trainings section is there for you to be able to preconfigure these trainings.

The configuration of a hypothetical 10K training might look like this:

goingrunning:
  trainings:
    10K:
      query:
        bpm: 160..180
        mood_aggressive: 0.6..
        ^genre: Reggae
      ordering:
        bpm: 100
        average_loudness: 50
      use_flavours: []
      duration: 60
      target: MPD1

query

The keys under the query section are exactly the same ones that you use when you are using beets for any other operation. Whatever is described in the beets query documentation applies here with two restriction: you must query specific fields in the form of field: value and (for now) regular expressions are not supported.

ordering

At the time being there is only one ordering algorithm (ScoreBasedLinearPermutation) which orders your songs based on a scoring system. What you indicate under the ordering section is the fields by which the songs will be ordered. Each field will have a weight from -100 to 100 indicating how important that field is with respect to the others. Negative numbers indicate a reverse ordering. (@todo: this probably needs an example.)

use_flavours

You will find that many of the query specification that you come up with will be repeated across different trainings. To reduce repetition and at the same time to be able to combine many different recipes you can use flavours. Similarly to targets, instead of defining the queries directly on your training you can define queries in a separate section called flavours (see below) and then use the use_flavours key to indicate which flavours to use. The order in which flavours are indicated is important: the first one has the highest priority meaning that it will overwrite any keys that might be found in subsequent flavours.

duration

The duration is expressed in minutes and serves the purpose of defining the total length of the training so that the plugin can select the exact number of songs.

target

This key indicates to which target (defined in the targets section) your songs will be copied to.

the fallback training

You might also define a special fallback training:

goingrunning:
  trainings:
    fallback:
      target: my_other_player

Any key not defined in a specific training will be looked up from the fallback training. So, if in the 10K example you were to remove the target key, it would be looked up from the fallback training and your songs would be copied to the my_other_device target.

Play count and favouring unplayed songs

In the default configuration of the plugin, on the fallback training there are two disabled options that you might want to consider enabling: increment_play_count and favour_unplayed. They are meant to be used together. The increment_play_count option, on copying your songs to your device, will increment the play_count attribute by one and store it in your library and on your media file. The favour_unplayed option will instruct the algorithm that picks the songs from your selection to favour the songs that have lower play_count. This feature will make you discover songs in your library that you might have never heard. At the same time it ensures that the proposed songs are always changed even if you keep your selection query and your ordering unchanged.

Flavours

The flavours section serves the purpose of defining named queries. If you have 5 different high intensity trainings different in length but sharing queries about bpm, mood and loudness, you can create a single definition here, called flavour, and reuse that flavour in your different trainings with the use_flavours key.

Note: Because flavours are only used to group query elements, the query key should not be used here (like it is in trainings).

goingrunning:
  flavours:
    overthetop:
      bpm: 170..
      mood_aggressive: 0.8..
      average_loudness: 50..
    rocker:
      genre: Rock
    metallic:
      genre: Metal
    sunshine:
      genre: Reggae
    60s:
      year: 1960..1969
    chillout:
      bpm: 1..120
      mood_happy: 0.5..1

This way, from the above flavours you might add use_flavours: [overthetop, rock, 60s] to one training and use_flavours: [overthetop, metallic] to another so they will share the same overthetop intensity definition whilst having different genre preferences. Similarly, your recovery session might use use_flavours: [chillout, sunshine].

Advanced queries

When it comes to handling queries, this plugin introduces some major differences with respect to the beets core you need to be aware of.

Recurring fields extend the selections

You might define different flavours in which some of the same fields are defined, like the genre field in the rocker and the metallic flavours above. You can define a training that makes use of those flavours and optionally adding the same field through a direct query section, like this:

goingrunning:
  trainings:
    HM:
      query:
        genre: Folk
      use_flavours: [rocker, metallic]

The resulting query will include songs corresponding to any of the three indicated genres: genre='Folk' OR genre='Rock' OR genre='Metal'. This, of course, is applicable to all fields.

Fields can be used as lists

Sometimes it is cumbersome to define a separate flavour for each additional value of a specific field. For example, it would be nice to have the above chillout flavour to include a list of genres instead of having to combine it with multiple flavours. Well, you can just do that by using the list notation like this:

goingrunning:
  flavours:
    chillout:
      bpm: 1..120
      mood_happy: 0.5..1
      genre: [Soul, Oldies, Ballad]

or like this:

goingrunning:
  flavours:
    chillout:
      bpm: 1..120
      mood_happy: 0.5..1
      genre:
        - Soul
        - Oldies
        - Ballad

The resulting query will have the same effect including all indicated genres: genre='Soul' OR genre='Oldies' OR genre='Ballad'. This technique can be applied to all fields.

Negated fields can also be used as lists

What is described above also applies to negated fields. That is to say, you can also negate a field and use it as a list to query your library by excluding all those values:

goingrunning:
  flavours:
    not_good_for_running:
      ^genre: [Jazz, Psychedelic Rock, Gospel]

When the above flavour is compiled it will result in a query excluding all indicated genres: genre!='Jazz' AND genre!='Psychedelic Rock' AND genre!='Gospel'. This technique can be applied to all fields.

Using a separate configuration file

In my experience the configuration section can grow quite long depending on your needs, so I find it useful to keep my goingrunning specific configuration in a separate file and from the main configuration file include it like this:

include:
  - plg_goingrunning.yaml

Examples

Show all the configured trainings:

$ beet goingrunning --list

Check what would be done for the 10K training:

$ beet goingrunning 10K --dry-run

Let's go! Copy your songs to your device based on the 10K training and using the plugin shorthand:

$ beet run 10K

Do the same as above but today you feel Ska:

$ beet run 10K genre:ska

Issues

  • If something is not working as expected please use the Issue tracker.
  • If the documentation is not clear please use the Issue tracker.
  • If you have a feature request please use the Issue tracker.
  • In any other situation please use the Issue tracker.

Roadmap

Please check the ROADMAP file. If there is a feature you would like to see but which is not planned, create a feature request in the Issue tracker.

Other plugins by the same author

Final Remarks

Enjoy!

beetsplugingoingrunning's People

Contributors

adamjakab avatar juanmeleiro avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

juanmeleiro

beetsplugingoingrunning's Issues

incompatibility issue with other plugins declaring the same types

When both acousticbrainz and goingrunning plugins are activated beets exits with the following error:

Traceback (most recent call last):
  File "/Users/jackisback/opt/miniconda3/envs/beetsdevel/bin/beet", line 8, in <module>
    sys.exit(main())
  File "/Users/jackisback/opt/miniconda3/envs/beetsdevel/lib/python3.7/site-packages/beets/ui/__init__.py", line 1266, in main
    _raw_main(args)
  File "/Users/jackisback/opt/miniconda3/envs/beetsdevel/lib/python3.7/site-packages/beets/ui/__init__.py", line 1249, in _raw_main
    subcommands, plugins, lib = _setup(options, lib)
  File "/Users/jackisback/opt/miniconda3/envs/beetsdevel/lib/python3.7/site-packages/beets/ui/__init__.py", line 1148, in _setup
    library.Item._types.update(plugins.types(library.Item))
  File "/Users/jackisback/opt/miniconda3/envs/beetsdevel/lib/python3.7/site-packages/beets/plugins.py", line 344, in types
    u'another type.'.format(plugin.name, field)
beets.plugins.PluginConflictException: Plugin acousticbrainz defines flexible field average_loudness which has already been defined with another type.

Removing one of either plugins, the problem goes away.

training without a target

when listing a training:

================================[   walking   ]=================================
duration: 3:00:00
query: genre(jazz)
ordering: year+(100)

user does not get warned about a missing target.

In fact upon beet run walking the message is not very clear:

Handling training: walking
The target name[None] is not defined!
The target name[None] is not defined!
The target[None] does not declare a device root path.

A better message would be:

Training 'walking' must define a target!

fallback configuration is not picked up for ordering

when using this configuration:

trainings:
        fallback:
            target: TEST
            increment_play_count: yes
            favour_unplayed: yes
            ordering:
                original_year: 25
                bpm: 50
                mood_aggressive: 50
                danceable: 100
                mood_relaxed: -100
        longrun-la:
            use_flavours: [running, intensity_low, genre_alternative]
            duration: 150
            target: SONY

and executing beet -vv run longrun-la

the plugin reports:

user configuration: /Users/jackisback/.config/beets/config.yaml
data directory: /Users/jackisback/.config/beets
plugin paths: /Users/jackisback/Documents/Projects/Python/BeetsPluginGenreFixer/beetsplug
Sending event: pluginload
inline: adding item field initial_letter
inline: adding item field disc_num_prefix
library database: /Users/jackisback/.config/beets/real_library.db
library directory: /Volumes/J/Music
Sending event: library_opened
goingrunning: Handling training: longrun-la
goingrunning: Getting attribute[device_root] for target: SONY
goingrunning: Finding target: SONY
goingrunning: Found target[SONY] attribute[device_root] path: /Volumes/WALKMAN
goingrunning: Getting attribute[device_path] for target: SONY
goingrunning: Finding target: SONY
goingrunning: Found target[SONY] attribute[device_path] path: MUSIC/AUTO
goingrunning: Found target[SONY] path: SONY
goingrunning: Command query elements: []
goingrunning: Training query elements: []
goingrunning: Flavour query elements: ["^genre:Experimental", "^genre:Christian", "^genre:Christmas", "^genre:Speech", "bpm:..110", "mood_aggressive:..0.1", "genre:Alternative Rock", "genre:Grunge", "genre:Indie", "genre:New Wave", "genre:Progressive", "genre:Psychedelic Rock", "genre:Punk", "genre:Ska"]
goingrunning: Combined query elements: ["^genre:Experimental", "^genre:Christian", "^genre:Christmas", "^genre:Speech", "bpm:..110", "mood_aggressive:..0.1", "genre:Alternative Rock", "genre:Grunge", "genre:Indie", "genre:New Wave", "genre:Progressive", "genre:Psychedelic Rock", "genre:Punk", "genre:Ska"]
goingrunning: ^genre: AndQuery([NotQuery(SubstringQuery("genre", "Experimental", True)), NotQuery(SubstringQuery("genre", "Christian", True)), NotQuery(SubstringQuery("genre", "Christmas", True)), NotQuery(SubstringQuery("genre", "Speech", True))])
goingrunning: bpm: NumericQuery("bpm", "..110", True)
goingrunning: mood_aggressive: NumericQuery("mood_aggressive", "..0.1", False)
goingrunning: genre: OrQuery([SubstringQuery("genre", "Alternative Rock", True), SubstringQuery("genre", "Grunge", True), SubstringQuery("genre", "Indie", True), SubstringQuery("genre", "New Wave", True), SubstringQuery("genre", "Progressive", True), SubstringQuery("genre", "Psychedelic Rock", True), SubstringQuery("genre", "Punk", True), SubstringQuery("genre", "Ska", True)])
goingrunning: Parsed query: AndQuery([AndQuery([NotQuery(SubstringQuery("genre", "Experimental", True)), NotQuery(SubstringQuery("genre", "Christian", True)), NotQuery(SubstringQuery("genre", "Christmas", True)), NotQuery(SubstringQuery("genre", "Speech", True))]), NumericQuery("bpm", "..110", True), NumericQuery("mood_aggressive", "..0.1", False), OrQuery([SubstringQuery("genre", "Alternative Rock", True), SubstringQuery("genre", "Grunge", True), SubstringQuery("genre", "Indie", True), SubstringQuery("genre", "New Wave", True), SubstringQuery("genre", "Progressive", True), SubstringQuery("genre", "Psychedelic Rock", True), SubstringQuery("genre", "Punk", True), SubstringQuery("genre", "Ska", True)])])
goingrunning: ORDERING permutation: ScoreBasedLinearPermutation
goingrunning: Scoring 753 items...
goingrunning: ORDER INFO: {}
goingrunning: PICKER strategy: RandomFromBinsPicker ("favour_unplayed": yes)

so the ordering key is not picked up from the fallback training. (It howvever seems that the favour_unplayed is picked up).

GoingRunning(beets-goingrunning) plugin for Beets: v1.2.1

Store multiple trainings on target

One might do multiple consecutive trainings which will make it necessary to store multiple trainings. For example: warmup, 10K, cool-down.
Instead of clean_target: yes|no we could have clean_target: no|training|all

Training without duration

when using a training without duration the plugin exits with the following back trace:

(base) jakabimac:~ jackisback$ beet run shopping
Handling training: shopping
Traceback (most recent call last):
  File "/Users/jackisback/opt/miniconda3/bin/beet", line 8, in <module>
    sys.exit(main())
  File "/Users/jackisback/opt/miniconda3/lib/python3.7/site-packages/beets/ui/__init__.py", line 1266, in main
    _raw_main(args)
  File "/Users/jackisback/opt/miniconda3/lib/python3.7/site-packages/beets/ui/__init__.py", line 1253, in _raw_main
    subcommand.func(lib, suboptions, subargs)
  File "/Users/jackisback/opt/miniconda3/lib/python3.7/site-packages/beetsplug/goingrunning/command.py", line 126, in func
    self.handle_training()
  File "/Users/jackisback/opt/miniconda3/lib/python3.7/site-packages/beetsplug/goingrunning/command.py", line 168, in handle_training
    sel_items = self._get_items_for_duration(sorted_lib_items, duration)
  File "/Users/jackisback/opt/miniconda3/lib/python3.7/site-packages/beetsplug/goingrunning/command.py", line 324, in _get_items_for_duration
    est_num_songs = round(requested_duration * 60 / _avg)
TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'

Add tags to repository

Congrats on this plugin! I'm considering it for generating sport routine playlists.
Not really a bug or something, just here to recommend adding tags to this repository for improved visibility/reachability in GitHub (personally I like to go to this page periodically to see if new plugins appear).
I've seen beets plugins repos usually having these tags:

  • beets
  • beets-plugin

Configuration error!

Error message: configuration error: goingrunning.trainings.walking.target not found

goingrunning:
  song_bpm: [111, 222]
  song_len: [120, 240]
  duration: 120
  target: SONY
  targets:
    TEST: ~/Music/tmp/
    SONY: /Volumes/WALKMAN/MUSIC/AUTO/
  clean_target: true
  trainings:
    test:
      alias: "Born to run"
      song_bpm: [90, 150]
      song_len: [120, 300]
      duration: 180
      target: TEST
    walking:
      alias: "Born to run"
      song_bpm: [0, 130]
      song_len: [0, 600]
      duration: 180
    longrun:
      alias: "Born to run"
      song_bpm: [120, 150]
      song_len: [120, 300]
      duration: 180

beets version 1.4.9
Python version 3.7.6
plugins: convert, duplicates, fromfilename, goingrunning, info, inline, the, zero

goingrunning -l
Available trainings:
======================================== ::: test
alias: Born to run
duration: 180
song_bpm: [90, 150]
song_len: [120, 300]
target: TEST
======================================== ::: walking
alias: Born to run
duration: 180
song_bpm: [0, 130]
song_len: [0, 600]
target: SONY
======================================== ::: longrun
alias: Born to run
duration: 180
song_bpm: [120, 150]
song_len: [120, 300]
target: SONY

So configuration is correctly read

If I put the target directly on the training leaf it works.

Beets 1.5+ "beets.util.confit is deprecated; use confuse instead"

Hi Adam,

When your plugin is installed it gives the following error in Beets 1.5+ when running any command. Even if I run e.g. beet ls -a artist. Is this something you would be willing/have the time for to fix?

Thanks, Jan

/opt/local/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/beets/mediafile.py:19: UserWarning: beets.mediafile is deprecated; use mediafile instead
  warnings.warn("beets.mediafile is deprecated; use mediafile instead")
/opt/local/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/beets/util/confit.py:19: UserWarning: beets.util.confit is deprecated; use confuse instead
  warnings.warn("beets.util.confit is deprecated; use confuse instead")

add progress when exporting to target

Sometimes it can take a while to copy all the songs to the target device. An optional progress bar showing the current status and ETA would be nice.

Coveralls.io is unlinked

I cannot use Travis anymore (needs commercial license) so publishing coverage information to coveralls.io is broken.

Maintaining a consistent file type

I currently convert songs using the convert plugin and use some tags to export songs for my Walkman device, since it can only play mp3s. Is something like this possible when you export the files? maybe setting up a bit-rate and file type and convert files which aren't mp3s? Just a suggestion :)
Thanks a lot for this! Never thought we could use bpm like this 👍

genre matching is not exact

Matching needs to be exact and not regex otherwise "Rock" will also match "Rock & Roll" from another selection. For regular expressions "^" first char can be used to distinguish if regex is needed.

Duration as argument

It would be nice to be able to change on the fly the duration of a training. For example a longrun defined as 2hrs training at times can be also shorter or longer.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.