diff --git a/beetsplug/importadded.py b/beetsplug/importadded.py new file mode 100644 index 000000000..539853859 --- /dev/null +++ b/beetsplug/importadded.py @@ -0,0 +1,97 @@ +"""Populate an items `added` and `mtime` field by using the file modification +time (mtime) of the item's source file before import. +""" + +from __future__ import unicode_literals, absolute_import, print_function + +import logging +import os + +from beets import config +from beets import util +from beets.plugins import BeetsPlugin + +log = logging.getLogger('beets') + + +class ImportAddedPlugin(BeetsPlugin): + def __init__(self): + super(ImportAddedPlugin, self).__init__() + self.config.add({ + 'preserve_mtimes': False, + }) + + +@ImportAddedPlugin.listen('import_task_start') +def check_config(task, session): + config['importadded']['preserve_mtimes'].get(bool) + + +def write_file_mtime(path, mtime): + """Write the given mtime to the destination path. + """ + stat = os.stat(util.syspath(path)) + os.utime(util.syspath(path), + (stat.st_atime, mtime)) + +# key: item path in the library +# value: the file mtime of the file the item was imported from +item_mtime = dict() + + +def write_item_mtime(item, mtime): + """Write the given mtime to an item's `mtime` field and to the mtime of the + item's file. + """ + if mtime is None: + log.warn("No mtime to be preserved for item " + + util.displayable_path(item.path)) + return + + # The file's mtime on disk must be in sync with the item's mtime + write_file_mtime(util.syspath(item.path), mtime) + item.mtime = mtime + + +@ImportAddedPlugin.listen('before_item_moved') +@ImportAddedPlugin.listen('item_copied') +def record_import_mtime(item, source, destination): + """Record the file mtime of an item's path before import. + """ + if (source == destination): + # Re-import of an existing library item? + return + + mtime = os.stat(util.syspath(source)).st_mtime + item_mtime[destination] = mtime + log.debug('Recorded mtime %s for item "%s" imported from "%s"', + mtime, + util.displayable_path(destination), + util.displayable_path(source)) + + +@ImportAddedPlugin.listen('album_imported') +def update_album_times(lib, album): + album_mtimes = [] + for item in album.items(): + mtime = item_mtime[item.path] + if mtime is not None: + album_mtimes.append(mtime) + if config['importadded']['preserve_mtimes'].get(bool): + write_item_mtime(item, mtime) + item.store() + del item_mtime[item.path] + + album.added = min(album_mtimes) + album.store() + + +@ImportAddedPlugin.listen('item_imported') +def update_item_times(lib, item): + mtime = item_mtime[item.path] + if mtime is not None: + item.added = mtime + if config['importadded']['preserve_mtimes'].get(bool): + write_item_mtime(item, mtime) + item.store() + del item_mtime[item.path] diff --git a/docs/plugins/importadded.rst b/docs/plugins/importadded.rst new file mode 100644 index 000000000..5da919325 --- /dev/null +++ b/docs/plugins/importadded.rst @@ -0,0 +1,33 @@ +ImportAdded Plugin +================== + +The ``importadded`` plugin is useful when an existing collection is imported +and the time when albums and items were added should be preserved. + +The :abbr:`mtime (modification time)` of files that are imported into the +library are assumed to represent the time when the items were originally +added. + +The ``item.added`` field is populated as follows: + +* For singleton items with no album, ``item.added`` is set to the item's file + mtime before it was imported. +* For items that are part of an album, ``album.added`` and ``item.added`` is + set to the oldest mtime of the files in the album before they were imported. + The mtime of album directories are ignored. + +This plugin can optionally be configured to also preserve mtimes:: + + importadded: + preserve_mtimes: yes # default: no + +File modification times are preserved as follows: + +* For all items: + + * ``item.mtime`` is set to the mtime of the file + from which the item is imported from. + * The mtime of the file ``item.path`` is set to ``item.mtime``. + +Note that there is no ``album.mtime`` field in the database and that the mtime +of album directories on disk aren't preserved. diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index eab7a69b9..7dc0fe799 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -59,6 +59,7 @@ by typing ``beet version``. ftintitle keyfinder bucket + importadded Autotagger Extensions --------------------- @@ -91,6 +92,8 @@ Metadata statistics (last_played, play_count, skip_count, rating). * :doc:`keyfinder`: Use the `KeyFinder`_ program to detect the musical key from the audio. +* :doc:`importadded`: Use file modification times for guessing the value for + the `added` field in the database. .. _Acoustic Attributes: http://developer.echonest.com/acoustic-attributes.html .. _the Echo Nest: http://www.echonest.com