diff --git a/beetsplug/convert.py b/beetsplug/convert.py index 6ed139da0..47ae9fbba 100644 --- a/beetsplug/convert.py +++ b/beetsplug/convert.py @@ -114,6 +114,7 @@ class ConvertPlugin(BeetsPlugin): self.config.add({ u'dest': None, u'pretend': False, + u'link': False, u'threads': util.cpu_count(), u'format': u'mp3', u'id3v23': u'inherit', @@ -167,6 +168,9 @@ class ConvertPlugin(BeetsPlugin): help=u'set the target format of the tracks') cmd.parser.add_option('-y', '--yes', action='store_true', dest='yes', help=u'do not ask for confirmation') + cmd.parser.add_option('-l', '--link', action='store_true', dest='link', + help=u'symlink files that do not need transcoding. \ + Don\'t use with \'embed\'.') cmd.parser.add_album_option() cmd.func = self.convert_func return [cmd] @@ -251,7 +255,7 @@ class ConvertPlugin(BeetsPlugin): util.displayable_path(source)) def convert_item(self, dest_dir, keep_new, path_formats, fmt, - pretend=False): + pretend=False, link=False): """A pipeline thread that converts `Item` objects from a library. """ @@ -304,15 +308,26 @@ class ConvertPlugin(BeetsPlugin): except subprocess.CalledProcessError: continue else: - if pretend: - self._log.info(u'cp {0} {1}', - util.displayable_path(original), - util.displayable_path(converted)) + if link: + if pretend: + self._log.info(u'ln -s {0} {1}', + util.displayable_path(original), + util.displayable_path(converted)) + else: + # No transcoding necessary. + self._log.info(u'Linking {0}', + util.displayable_path(item.path)) + util.link(original, converted) else: - # No transcoding necessary. - self._log.info(u'Copying {0}', - util.displayable_path(item.path)) - util.copy(original, converted) + if pretend: + self._log.info(u'cp {0} {1}', + util.displayable_path(original), + util.displayable_path(converted)) + else: + # No transcoding necessary. + self._log.info(u'Copying {0}', + util.displayable_path(item.path)) + util.copy(original, converted) if pretend: continue @@ -346,7 +361,8 @@ class ConvertPlugin(BeetsPlugin): plugins.send('after_convert', item=item, dest=converted, keepnew=False) - def copy_album_art(self, album, dest_dir, path_formats, pretend=False): + def copy_album_art(self, album, dest_dir, path_formats, pretend=False, + link=False): """Copies or converts the associated cover art of the album. Album must have at least one track. """ @@ -399,15 +415,26 @@ class ConvertPlugin(BeetsPlugin): if not pretend: ArtResizer.shared.resize(maxwidth, album.artpath, dest) else: - if pretend: - self._log.info(u'cp {0} {1}', - util.displayable_path(album.artpath), - util.displayable_path(dest)) + if link: + if pretend: + self._log.info(u'ln -s {0} {1}', + util.displayable_path(album.artpath), + util.displayable_path(dest)) + else: + self._log.info(u'Linking cover art from {0} to {1}', + util.displayable_path(album.artpath), + util.displayable_path(dest)) + util.link(album.artpath, dest) else: - self._log.info(u'Copying cover art from {0} to {1}', - util.displayable_path(album.artpath), - util.displayable_path(dest)) - util.copy(album.artpath, dest) + if pretend: + self._log.info(u'cp {0} {1}', + util.displayable_path(album.artpath), + util.displayable_path(dest)) + else: + self._log.info(u'Copying cover art from {0} to {1}', + util.displayable_path(album.artpath), + util.displayable_path(dest)) + util.copy(album.artpath, dest) def convert_func(self, lib, opts, args): dest = opts.dest or self.config['dest'].get() @@ -426,6 +453,11 @@ class ConvertPlugin(BeetsPlugin): else: pretend = self.config['pretend'].get(bool) + if opts.link is not None: + link = opts.link + else: + link = self.config['link'].get(bool) + if opts.album: albums = lib.albums(ui.decargs(args)) items = [i for a in albums for i in a.items()] @@ -446,13 +478,14 @@ class ConvertPlugin(BeetsPlugin): if opts.album and self.config['copy_album_art']: for album in albums: - self.copy_album_art(album, dest, path_formats, pretend) + self.copy_album_art(album, dest, path_formats, pretend, link) convert = [self.convert_item(dest, opts.keep_new, path_formats, fmt, - pretend) + pretend, + link) for _ in range(threads)] pipe = util.pipeline.Pipeline([iter(items), convert]) pipe.run_parallel() diff --git a/docs/changelog.rst b/docs/changelog.rst index e394a4cd9..310e5fb0d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -11,6 +11,9 @@ New features: (the MBID), and ``work_disambig`` (the disambiguation string). Thanks to :user:`dosoe`. :bug:`2580` :bug:`3272` +* :doc:`/plugins/convert`: Added new ``-l`` (``--link``) flag and ``link`` option + which symlinks files that do not need to be converted instead of copying them. + :bug:`2324` * :doc:`/plugins/bpd`: BPD now supports most of the features of version 0.16 of the MPD protocol. This is enough to get it talking to more complicated clients like ncmpcpp, but there are still some incompatibilities, largely due diff --git a/docs/plugins/convert.rst b/docs/plugins/convert.rst index 72ea301f1..775434d66 100644 --- a/docs/plugins/convert.rst +++ b/docs/plugins/convert.rst @@ -48,6 +48,10 @@ To test your configuration without taking any actions, use the ``--pretend`` flag. The plugin will print out the commands it will run instead of executing them. +By default, files that do not need to be transcoded will be copied to their +destination. Passing the ``-l`` (``--link``) flag creates symbolic links +instead. Refer to the ``link`` option below for potential issues with this. + Configuration ------------- @@ -93,6 +97,13 @@ file. The available options are: - **threads**: The number of threads to use for parallel encoding. By default, the plugin will detect the number of processors available and use them all. +- **link**: By default, files that do not need to be transcoded will be copied + to their destination. This option creates symbolic links instead. Note that + options such as ``embed`` that modify the output files after the transcoding + step will cause the original files to be modified as well if ``link`` is + enabled. For this reason, it is highly recommended not use to ``link`` and + ``embed`` at the same time. + Default: ``false``. You can also configure the format to use for transcoding (see the next section):