Skip to content
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

added 3rd party app to docs #2641

Open
wants to merge 4 commits into
base: master
from

Conversation

@kdahlhaus
Copy link

kdahlhaus commented Jul 23, 2017

This is a simple demonstration of using beets as a music library in a 3rd party python application.

@sampsyo

This comment has been minimized.

Copy link
Member

sampsyo commented Jul 24, 2017

Huh, that’s very cool! It has always been a goal to let people import beets, but we don’t have a lot of instructions on how to do that.

What would you think of adding this stuff to the documentation instead of as a standalone file? We could expand on how to use each piece to do what you want.

@kdahlhaus

This comment has been minimized.

Copy link
Author

kdahlhaus commented Jul 24, 2017

Sure, that makes sense. I'll take a crack at moving into the docs instead.

@Stunner

This comment has been minimized.

Copy link
Contributor

Stunner commented Aug 28, 2017

This demo project is incredibly helpful, I've been meaning to write a python script to compliment beets and this will save me a lot of time I would have otherwise spending figuring out exactly how to go about this. I personally find examples incredibly helpful. Perhaps you could post this code sample as a Gist on GitHub and then link to it from the beets docs?

@kdahlhaus

This comment has been minimized.

Copy link
Author

kdahlhaus commented Aug 31, 2017

@kdahlhaus kdahlhaus changed the title added 3rd party demo added 3rd party app to docs Sep 5, 2017
@rachmadaniHaryono

This comment has been minimized.

Copy link
Contributor

rachmadaniHaryono commented Oct 29, 2018

@kdahlhaus thanks for the code

here is another example to query database based on this code

>>> from beets import config
>>> from beets.ui import _open_library
>>> 
>>> music_library_file_name = 'library.db'
>>> config['library'] = music_library_file_name
>>> lib = _open_library(config)
>>> 
>>> items = lib.items('a1')
>>> vars(items[0])

{'_db': <beets.library.Library at 0x7f8c4525f160>,
 '_dirty': set(),
 '_values_fixed': {'id': 2002,
  'path': b'/home/<redacted, user>/Music/Non-Album/A1/No More.mp3',
  'album_id': None,
  'title': 'No More',
  'artist': 'A1',
  'artist_sort': 'A1',
  'artist_credit': 'A1',
  'album': '',
  'albumartist': '',
  'albumartist_sort': '',
  'albumartist_credit': '',
  'genre': 'Pop',
  'lyricist': '',
  'composer': '',
  'composer_sort': '',
  'arranger': '',
  'grouping': '',
  'year': 0,
  'month': 0,
  'day': 0,
  'track': 0,
  'tracktotal': 0,
  'disc': 0,
  'disctotal': 0,
  'lyrics': "<redacted>",
  'comments': '',
  'bpm': 0,
  'comp': 0,
  'mb_trackid': 'ffd801db-8125-4650-bd93-085e1a660950',
  'mb_albumid': '',
  'mb_artistid': 'dfd8ee47-6169-403a-be9e-31c75293280e',
  'mb_albumartistid': '',
  'albumtype': '',
  'label': '',
  'acoustid_fingerprint': '<redacted>',
  'acoustid_id': 'd04c306c-82bf-4c57-ad24-15694c07ae5f',
  'mb_releasegroupid': '',
  'asin': '',
  'catalognum': '',
  'script': '',
  'language': '',
  'country': '',
  'albumstatus': '',
  'media': '',
  'albumdisambig': '',
  'disctitle': '',
  'encoder': '',
  'rg_track_gain': None,
  'rg_track_peak': None,
  'rg_album_gain': None,
  'rg_album_peak': None,
  'r128_track_gain': None,
  'r128_album_gain': None,
  'original_year': 0,
  'original_month': 0,
  'original_day': 0,
  'initial_key': None,
  'length': 220.992,
  'bitrate': 128965,
  'format': 'MP3',
  'samplerate': 48000,
  'bitdepth': 0,
  'channels': 2,
  'mtime': 1540477839.0,
  'added': 1540477835.0772004,
  'mb_releasetrackid': '',
  'releasegroupdisambig': ''},
 '_values_flex': {'data_source': 'MusicBrainz',
  'danceable': '0.986882865429',
  'gender': 'female',
  'genre_rosamerica': 'pop',
  'mood_acoustic': '0.227805525064',
  'mood_aggressive': '0.0813837423921',
  'mood_electronic': '0.792393743992',
  'mood_happy': '0.352537900209',
  'mood_party': '0.452083617449',
  'mood_relaxed': '0.160328432918',
  'mood_sad': '0.409968942404',
  'rhythm': 'ChaChaCha',
  'tonal': '0.975805878639',
  'voice_instrumental': 'voice',
  'average_loudness': '0.800887823105',
  'chords_changes_rate': '0.0677460581064',
  'chords_key': 'C',
  'chords_number_rate': '0.00234341714531',
  'chords_scale': 'minor',
  'key_strength': '0.697014629841'}}
@kdahlhaus

This comment has been minimized.

Copy link
Author

kdahlhaus commented Oct 29, 2018

here is another example to query database based on this code

Very nice! Thanks for the example.

@rachmadaniHaryono

This comment has been minimized.

Copy link
Contributor

rachmadaniHaryono commented Dec 25, 2018

i just play around beets code source.

there should be documented somewhere that beets accept args on its main func

# $ beets --version
# is equivalent to this:
from beets.ui import main
from beets.ui.commands import show_version
main(['--version'])

use shlex.split doc: https://docs.python.org/3/library/shlex.html to parse more complex args, for example:

# $ beets import -s --move track_title-track_artist.mp3
# is equivalent to this:
import shlex
from beets.ui import main
mp3_path = 'track_title-track_artist.mp3'
from_shlex_args = shlex.split("import -s --move '{}'".format(mp3_path))
main(from_shlex_args)

for anyone want to learn more about beets functions,
just use pdb before main function and step into it.

for example i just recently learn that

from beets.ui import main
from beets.ui.commands import show_version
main(['--version'])
# is equivalent to 
show_version(None, None, None)

more complex function call

import optparse
import shlex
import six
from beets import config, util
from beets.ui.__init__ import SubcommandsOptionParser, _setup
from beets.ui.commands import import_files
# START: taken from `beets.ui.__init__._raw_main`
# additional options is added because
# it will raise error when import file aborted
parser = SubcommandsOptionParser()
parser.add_option(
    '-h', '--help', dest='help', action='store_true',
    help=u'show this help message and exit')
parser.add_option(
    '--version', dest='version', action='store_true',
    help=optparse.SUPPRESS_HELP)
options, subargs = parser.parse_global_options(from_shlex_args)  # return: options, subargs
#  `parse_global_options` func require help and version option
subcommands, _, lib = _setup(options)  # return: subcommands, plugins, lib
parser.add_subcommand(*subcommands)
_, suboptions, _ = parser.parse_subcommand(subargs)  # return: subcommand, suboptions, subargs
# END
# START: taken from `beets.ui.commands.import_func`
# for available value of config see config_default.yaml
config['import'].set_args(suboptions)
# Special case: --copy flag suppresses import_move (which would
# otherwise take precedence).
if opts.copy:
    config['import']['move'] = False
query = None
paths = [mp3_path]
# On Python 2, we get filenames as raw bytes, which is what we
# need. On Python 3, we need to undo the "helpful" conversion to
# Unicode strings to get the real bytestring filename.
if not six.PY2:
    paths = [p.encode(util.arg_encoding(), 'surrogateescape') for p in paths]
# END
config['threaded'] = False  # NOTE set this so it can be easily debugged
import_files(lib, paths, query)

how to get lib instance from beets, from above section

import optparse
from beets.ui.__init__ import SubcommandsOptionParser, _setup
parser = SubcommandsOptionParser()
parser.add_option(
    '-h', '--help', dest='help', action='store_true',
    help=u'show this help message and exit')
parser.add_option(
    '--version', dest='version', action='store_true',
    help=optparse.SUPPRESS_HELP)
options, _ = parser.parse_global_options([])  # return: options, subargs
#  `parse_global_options` func require help and version option
_, _, lib = _setup(options)  # return: subcommands, plugins, lib

@sampsyo is it possible to have SubcommandsOptionParser.parse_global_options
to receive empty args directly?

# so instead:
if options.help:
    subargs = ['help']
elif options.version:
    subargs = ['version']
return options, subargs
# use this
if hasattr(options, 'help') and options.help:
    subargs = ['help']
elif hasattr(options, 'version') and options.version:
    subargs = ['version']
return options, subargs
# result
from beets.ui.__init__ import SubcommandsOptionParser, _setup
subcommands, plugins, lib = _setup(SubcommandsOptionParser().parse_global_options([])[0])
@rachmadaniHaryono

This comment has been minimized.

Copy link
Contributor

rachmadaniHaryono commented Jan 1, 2019

this is another snippet to get recommendation beets when given mp3 file path

>>> #  programatically get candidate
>>> from unittest.mock import patch, Mock
>>> import os
>>> import shlex
>>> from beets import config, plugins
>>> from beets.importer import action
>>> from beets.ui import main
>>> from beets.util import syspath, normpath
>>> from beets.ui.commands import TerminalImportSession
>>> class CustomTerminalImportSession(TerminalImportSession):
>>> 
>>>     def choose_item(self, task):
>>>         self.task = task
>>>         return action.SKIP
>>> 
>>> class CustomClass:
>>> 
>>>     def custom_import_files(self, lib, paths, query):
>>>         """Import the files in the given list of paths or matching the
>>>         query.
>>>         """
>>>         # Check the user-specified directories.
>>>         for path in paths:
>>>             if not os.path.exists(syspath(normpath(path))):
>>>                 raise ui.UserError(u'no such file or directory: {0}'.format(
>>>                     displayable_path(path)))
>>> 
>>>         # Check parameter consistency.
>>>         if config['import']['quiet'] and config['import']['timid']:
>>>             raise ui.UserError(u"can't be both quiet and timid")
>>> 
>>>         # Open the log.
>>>         if config['import']['log'].get() is not None:
>>>             logpath = syspath(config['import']['log'].as_filename())
>>>             try:
>>>                 loghandler = logging.FileHandler(logpath)
>>>             except IOError:
>>>                 raise ui.UserError(u"could not open log file for writing: "
>>>                                    u"{0}".format(displayable_path(logpath)))
>>>         else:
>>>             loghandler = None
>>> 
>>>         # Never ask for input in quiet mode.
>>>         if config['import']['resume'].get() == 'ask' and \
>>>                 config['import']['quiet']:
>>>             config['import']['resume'] = False
>>> 
>>>         session = CustomTerminalImportSession(lib, loghandler, paths, query)
>>>         session.run()
>>>         self.task = session.task
>>> 
>>>         # Emit event.
>>>         plugins.send('import', lib=lib, paths=paths)
>>> 
>>> c1 = CustomClass()
>>> with patch('beets.ui.commands.import_files', side_effect=c1.custom_import_files):
>>>     mp3_path = 'artist - title.mp3'
>>>     from_shlex_args = shlex.split("import -s --move '{}'".format(mp3_path))
>>>     main(from_shlex_args)
>>>     task = c1.task
>>> task
{'candidates': [TrackMatch(distance=<beets.autotag.hooks.Distance object at 0x7f997d1bd710>, info=<beets.autotag.hooks.TrackInfo object at 0x7f997d1bd4a8>),
                TrackMatch(distance=<beets.autotag.hooks.Distance object at 0x7f997d1b9c88>, info=<beets.autotag.hooks.TrackInfo object at 0x7f997d1b9518>),
                TrackMatch(distance=<beets.autotag.hooks.Distance object at 0x7f997d1bddd8>, info=<beets.autotag.hooks.TrackInfo object at 0x7f997d1bdd68>),
                TrackMatch(distance=<beets.autotag.hooks.Distance object at 0x7f997d1bd208>, info=<beets.autotag.hooks.TrackInfo object at 0x7f997d1bd358>),
                TrackMatch(distance=<beets.autotag.hooks.Distance object at 0x7f997d1bd780>, info=<beets.autotag.hooks.TrackInfo object at 0x7f997d1bdda0>)],
 'choice_flag': <action.SKIP: 1>,
 'cur_album': None,
 'cur_artist': None,
 'is_album': False,
 'item': Item(...),
 'items': [Item(...)],
 'match': None,
 'paths': [b'...'
           b'...'],
 'rec': <Recommendation.none: 0>,
 'search_ids': [],
 'should_merge_duplicates': False,
 'should_remove_duplicates': False,
 'toppath': b'...'
            b'...'}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants
You can’t perform that action at this time.