mirror of
https://github.com/beetbox/beets.git
synced 2025-12-15 21:14:19 +01:00
Merge branch 'convert'
This commit is contained in:
commit
b05fc8ef4f
3 changed files with 205 additions and 0 deletions
149
beetsplug/convert.py
Normal file
149
beetsplug/convert.py
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
"""Converts tracks or albums to external directory
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
import shutil
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
import imghdr
|
||||
|
||||
from beets.plugins import BeetsPlugin
|
||||
from beets import ui, library, util, mediafile
|
||||
from beets.util.functemplate import Template
|
||||
|
||||
log = logging.getLogger('beets')
|
||||
DEVNULL = open(os.devnull, 'wb')
|
||||
conf = {}
|
||||
|
||||
def _embed(path, items):
|
||||
"""Embed an image file, located at `path`, into each item.
|
||||
"""
|
||||
data = open(util.syspath(path), 'rb').read()
|
||||
kindstr = imghdr.what(None, data)
|
||||
if kindstr not in ('jpeg', 'png'):
|
||||
log.error('A file of type %s is not allowed as coverart.' % kindstr)
|
||||
return
|
||||
log.debug('Embedding album art.')
|
||||
for item in items:
|
||||
try:
|
||||
f = mediafile.MediaFile(syspath(item.path))
|
||||
except mediafile.UnreadableFileError as exc:
|
||||
log.warn('Could not embed art in {0}: {1}'.format(
|
||||
repr(item.path), exc
|
||||
))
|
||||
continue
|
||||
f.art = data
|
||||
f.save()
|
||||
|
||||
class encodeThread(threading.Thread):
|
||||
def __init__(self, source, dest, artpath):
|
||||
threading.Thread.__init__(self)
|
||||
self.source = source
|
||||
self.dest = dest
|
||||
self.artpath = artpath
|
||||
|
||||
def run(self):
|
||||
sema.acquire()
|
||||
self.encode()
|
||||
sema.release()
|
||||
|
||||
dest_item = library.Item.from_path(self.source)
|
||||
dest_item.path = self.dest
|
||||
dest_item.write()
|
||||
if self.artpath and conf['embed']:
|
||||
_embed(self.artpath,[dest_item])
|
||||
|
||||
def encode(self):
|
||||
log.info('Started encoding '+ self.source)
|
||||
temp_dest = self.dest + '~'
|
||||
|
||||
source_ext = os.path.splitext(self.source)[1].lower()
|
||||
if source_ext == '.flac':
|
||||
decode = Popen([conf['flac'], '-c', '-d', '-s', self.source],
|
||||
stdout=PIPE)
|
||||
encode = Popen([conf['lame']] + conf['opts'] + ['-', temp_dest],
|
||||
stdin=decode.stdout, stderr=DEVNULL)
|
||||
decode.stdout.close()
|
||||
encode.communicate()
|
||||
elif source_ext == '.mp3':
|
||||
encode = Popen([conf['lame']] + conf['opts'] + ['--mp3input'] +
|
||||
[self.source, temp_dest], close_fds=True, stderr=DEVNULL)
|
||||
encode.communicate()
|
||||
else:
|
||||
log.error('Only converting from FLAC or MP3 implemented')
|
||||
return
|
||||
shutil.move(temp_dest, self.dest)
|
||||
log.info('Finished encoding '+ self.source)
|
||||
|
||||
|
||||
def convert_item(lib, item, dest_dir, artpath):
|
||||
if item.format != 'FLAC' and item.format != 'MP3':
|
||||
log.info('Skipping {0} : not supported format'.format(item.path))
|
||||
return
|
||||
|
||||
dest = os.path.join(dest_dir,lib.destination(item, fragment = True))
|
||||
dest = os.path.splitext(dest)[0] + '.mp3'
|
||||
|
||||
if not os.path.exists(dest):
|
||||
util.mkdirall(dest)
|
||||
if item.format == 'MP3' and item.bitrate < 1000*conf['max_bitrate']:
|
||||
log.info('Copying {0}'.format(item.path))
|
||||
shutil.copy(item.path, dest)
|
||||
if artpath and conf['embed']:
|
||||
_embed(artpath,[library.Item.from_path(dest)])
|
||||
else:
|
||||
thread = encodeThread(item.path, dest, artpath)
|
||||
thread.start()
|
||||
else:
|
||||
log.info('Skipping {0} : target file exists'.format(item.path))
|
||||
|
||||
|
||||
def convert_func(lib, config, opts, args):
|
||||
global sema
|
||||
|
||||
dest = opts.dest if opts.dest is not None else conf['dest']
|
||||
if not dest:
|
||||
log.error('No destination set')
|
||||
return
|
||||
threads = opts.threads if opts.threads is not None else conf['threads']
|
||||
sema = threading.BoundedSemaphore(threads)
|
||||
|
||||
fmt = '$albumartist - $album' if opts.album \
|
||||
else '$artist - $album - $title'
|
||||
ui.commands.list_items(lib, ui.decargs(args), opts.album, False, fmt)
|
||||
|
||||
if not ui.input_yn("Convert? (Y/n)"):
|
||||
return
|
||||
|
||||
if opts.album:
|
||||
for album in lib.albums(ui.decargs(args)):
|
||||
for item in album.items():
|
||||
convert_item(lib, item, dest, album.artpath)
|
||||
else:
|
||||
for item in lib.items(ui.decargs(args)):
|
||||
convert_item(lib, item, dest, lib.get_album(item).artpath)
|
||||
|
||||
class ConvertPlugin(BeetsPlugin):
|
||||
def configure(self, config):
|
||||
conf['dest'] = ui.config_val(config, 'convert', 'dest', None)
|
||||
conf['threads'] = ui.config_val(config, 'convert', 'threads', 2)
|
||||
conf['flac'] = ui.config_val(config, 'convert', 'flac', 'flac')
|
||||
conf['lame'] = ui.config_val(config, 'convert', 'lame', 'lame')
|
||||
conf['opts'] = ui.config_val(config, 'convert',
|
||||
'opts', '-V2').split(' ')
|
||||
conf['max_bitrate'] = int(ui.config_val(config, 'convert',
|
||||
'max_bitrate','500'))
|
||||
conf['embed'] = ui.config_val(config, 'convert', 'embed', True,
|
||||
vtype = bool)
|
||||
|
||||
def commands(self):
|
||||
cmd = ui.Subcommand('convert', help='convert to external location')
|
||||
cmd.parser.add_option('-a', '--album', action='store_true',
|
||||
help='choose albums instead of tracks')
|
||||
cmd.parser.add_option('-t', '--threads', action='store', type='int',
|
||||
help='change the number of threads (default 2)')
|
||||
cmd.parser.add_option('-d', '--dest', action='store',
|
||||
help='set the destination directory')
|
||||
cmd.func = convert_func
|
||||
return [cmd]
|
||||
54
docs/plugins/convert.rst
Normal file
54
docs/plugins/convert.rst
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
Convert Plugin
|
||||
==============
|
||||
|
||||
The ``convert`` plugin lets you convert parts of your collection to a directory
|
||||
of your choice. Currently only converting from MP3 or FLAC to MP3 is supported.
|
||||
It will skip files that are already present in the target directory. It uses
|
||||
the same directory structure as your library.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
This plugin requires ``flac`` and ``lame``. If thoses executables are in your
|
||||
path, they will be found automatically by the plugin, otherwise you have to set
|
||||
their respective config options. Of course you will have to enable the plugin
|
||||
as well (see :doc:`/plugins/index`)::
|
||||
|
||||
[convert]
|
||||
flac:/usr/bin/flac
|
||||
lame:/usr/bin/lame
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
To convert a part of your collection simply run ``beet convert QUERY``. This
|
||||
will display all items matching ``QUERY`` and ask you for confirmation before
|
||||
starting the conversion. The ``-a`` (or ``--album``) option causes the command
|
||||
to match albums instead of tracks.
|
||||
|
||||
The ``-t`` (``--threads``) and ``-d`` (``--dest``) options allow you to specify
|
||||
or overwrite the respective configuration options.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
This plugin offers a couple of configuration options: If you want to disable
|
||||
that album art is embedded in your converted items (enabled by default), you
|
||||
will have to set the ``embed`` option to false. If you set ``max_bitrate``, all
|
||||
MP3 files with a higher bitrate will be converted and thoses with a lower
|
||||
bitrate will simply be copied. Be aware that this doesn't mean that your
|
||||
converted files will have a lower bitrate since that depends on the specified
|
||||
encoding options. By default only FLAC files will be converted (and all MP3s
|
||||
will be copied). ``opts`` are the encoding options that are passed to ``lame``
|
||||
(defaults to "-V2"). Please refer to the ``lame`` docs for possible options.
|
||||
|
||||
The ``dest`` sets the directory the files will be converted (or copied) to.
|
||||
Finally ``threads`` lets you determine the number of threads to use for
|
||||
encoding (default: 2). An example configuration::
|
||||
|
||||
[convert]
|
||||
embed:false
|
||||
max_bitrate:200
|
||||
opts:-V4
|
||||
dest:/home/user/MusicForPhone
|
||||
threads:4
|
||||
|
|
@ -54,6 +54,7 @@ disabled by default, but you can turn them on as described above.
|
|||
fuzzy_search
|
||||
zero
|
||||
ihate
|
||||
convert
|
||||
|
||||
Autotagger Extensions
|
||||
''''''''''''''''''''''
|
||||
|
|
@ -96,6 +97,7 @@ Miscellaneous
|
|||
* :doc:`ihate`: Skip by defined patterns things you hate during import process.
|
||||
* :doc:`bpd`: A music player for your beets library that emulates `MPD`_ and is
|
||||
compatible with `MPD clients`_.
|
||||
* :doc:`convert`: Converts parts of your collection to an external directory
|
||||
|
||||
.. _MPD: http://mpd.wikia.com/
|
||||
.. _MPD clients: http://mpd.wikia.com/wiki/Clients
|
||||
|
|
|
|||
Loading…
Reference in a new issue