Exemplo n.º 1
0
    def update(self, create=None):
        if not os.path.isdir(self.directory) and not self.ask_create(create):
            print_(u'Skipping creation of {0}'
                   .format(displayable_path(self.directory)))
            return

        converter = self.converter()
        for (item, action) in self.items_action():
            dest = self.destination(item)
            path = self.get_path(item)
            if action == self.MOVE:
                print_(u'>{0} -> {1}'.format(displayable_path(path),
                                             displayable_path(dest)))
                util.mkdirall(dest)
                util.move(path, dest)
                util.prune_dirs(os.path.dirname(path), root=self.directory)
                self.set_path(item, dest)
                item.store()
                item.write(path=dest)
            elif action == self.WRITE:
                print_(u'*{0}'.format(displayable_path(path)))
                item.write(path=path)
            elif action == self.ADD:
                print_(u'+{0}'.format(displayable_path(dest)))
                converter.submit(item)
            elif action == self.REMOVE:
                print_(u'-{0}'.format(displayable_path(path)))
                self.remove_item(item)
                item.store()

        for item, dest in converter.as_completed():
            self.set_path(item, dest)
            item.store()
        converter.shutdown()
Exemplo n.º 2
0
def encode(source, dest):
    log.info(u'Started encoding {0}'.format(util.displayable_path(source)))

    command = get_command()
    opts = []

    for arg in command:
        arg = arg.encode('utf-8')
        opts.append(Template(arg).substitute({
            'source':   source,
            'dest':     dest
        }))

    encode = Popen(opts, close_fds=True, stderr=DEVNULL)
    encode.wait()

    if encode.returncode != 0:
        # Something went wrong (probably Ctrl+C), remove temporary files
        log.info(u'Encoding {0} failed. Cleaning up...'
                 .format(util.displayable_path(source)))
        util.remove(dest)
        util.prune_dirs(os.path.dirname(dest))
        return

    log.info(u'Finished encoding {0}'.format(util.displayable_path(source)))
Exemplo n.º 3
0
def encode(source, dest):
    log.info('Started encoding ' + source)
    temp_dest = dest + '~'

    source_ext = os.path.splitext(source)[1].lower()
    if source_ext == '.flac':
        decode = Popen([conf['flac'], '-c', '-d', '-s', 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'] +
                       [source, temp_dest], close_fds=True, stderr=DEVNULL)
        encode.communicate()
    else:
        log.error('Only converting from FLAC or MP3 implemented')
        return
    if encode.returncode != 0:
        # Something went wrong (probably Ctrl+C), remove temporary files
        log.info('Encoding {0} failed. Cleaning up...'.format(source))
        util.remove(temp_dest)
        util.prune_dirs(os.path.dirname(temp_dest))
        return
    shutil.move(temp_dest, dest)
    log.info('Finished encoding ' + source)
Exemplo n.º 4
0
def encode(source, dest):
    quiet = config['convert']['quiet'].get()

    if not quiet:
        log.info(u'Started encoding {0}'.format(util.displayable_path(source)))

    command, _ = get_format()
    opts = []
    for arg in command:
        opts.append(Template(arg).safe_substitute({
            'source': source,
            'dest':   dest,
        }))

    log.debug(u'convert: executing: {0}'.format(
        u' '.join(pipes.quote(o.decode('utf8', 'ignore')) for o in opts)
    ))
    encode = Popen(opts, close_fds=True, stderr=DEVNULL)
    encode.wait()

    if encode.returncode != 0:
        # Something went wrong (probably Ctrl+C), remove temporary files
        log.info(u'Encoding {0} failed. Cleaning up...'
                 .format(util.displayable_path(source)))
        util.remove(dest)
        util.prune_dirs(os.path.dirname(dest))
        return

    if not quiet:
        log.info(u'Finished encoding {0}'.format(
            util.displayable_path(source))
        )
Exemplo n.º 5
0
    def update(self, create=None):
        if not os.path.isdir(self.directory) and not self.ask_create(create):
            print_(u'Skipping creation of {0}'.format(
                displayable_path(self.directory)))
            return

        converter = self.converter()
        for (item, action) in self.items_action():
            dest = self.destination(item)
            path = self.get_path(item)
            if action == self.MOVE:
                print_(u'>{0} -> {1}'.format(displayable_path(path),
                                             displayable_path(dest)))
                util.mkdirall(dest)
                util.move(path, dest)
                util.prune_dirs(os.path.dirname(path), root=self.directory)
                self.set_path(item, dest)
                item.store()
                item.write(path=dest)
            elif action == self.WRITE:
                print_(u'*{0}'.format(displayable_path(path)))
                item.write(path=path)
            elif action == self.ADD:
                print_(u'+{0}'.format(displayable_path(dest)))
                converter.submit(item)
            elif action == self.REMOVE:
                print_(u'-{0}'.format(displayable_path(path)))
                self.remove_item(item)
                item.store()

        for item, dest in converter.as_completed():
            self.set_path(item, dest)
            item.store()
        converter.shutdown()
Exemplo n.º 6
0
    def move_art(self, copy=False, link=False):
        """Move or copy any existing album art so that it remains in the
        same directory as the items.
        """
        old_art = self.artpath
        if not old_art:
            return

        new_art = self.art_destination(old_art)
        if new_art == old_art:
            return

        new_art = util.unique_path(new_art)
        log.debug(u'moving album art {0} to {1}',
                  util.displayable_path(old_art),
                  util.displayable_path(new_art))
        if copy:
            util.copy(old_art, new_art)
        elif link:
            util.link(old_art, new_art)
        else:
            util.move(old_art, new_art)
        self.artpath = new_art

        # Prune old path when moving.
        if not copy:
            util.prune_dirs(os.path.dirname(old_art),
                            self._db.directory)
Exemplo n.º 7
0
    def move(self, copy=False, basedir=None):
        """Moves (or copies) all items to their destination. Any
        album art moves along with them. basedir overrides the library
        base directory for the destination.
        """
        basedir = basedir or self._library.directory

        # Move items.
        items = list(self.items())
        for item in items:
            item.move(self._library, copy, basedir=basedir)
        newdir = os.path.dirname(items[0].path)

        # Move art.
        old_art = self.artpath
        if old_art:
            new_art = self.art_destination(old_art, newdir)
            if new_art != old_art:
                if copy:
                    util.copy(old_art, new_art)
                else:
                    util.move(old_art, new_art)
                self.artpath = new_art
            if not copy: # Prune old path.
                util.prune_dirs(os.path.dirname(old_art),
                                self._library.directory)

        # Store new item paths. We do this at the end to avoid
        # locking the database for too long while files are copied.
        for item in items:
            self._library.store(item)
Exemplo n.º 8
0
def encode(source, dest):
    quiet = config['convert']['quiet'].get()

    if not quiet:
        log.info(u'Started encoding {0}'.format(util.displayable_path(source)))

    command, _ = get_format()
    opts = []
    for arg in command:
        opts.append(
            Template(arg).safe_substitute({
                'source': source,
                'dest': dest,
            }))

    log.debug(u'convert: executing: {0}'.format(u' '.join(
        pipes.quote(o.decode('utf8', 'ignore')) for o in opts)))
    encode = Popen(opts, close_fds=True, stderr=DEVNULL)
    encode.wait()

    if encode.returncode != 0:
        # Something went wrong (probably Ctrl+C), remove temporary files
        log.info(u'Encoding {0} failed. Cleaning up...'.format(
            util.displayable_path(source)))
        util.remove(dest)
        util.prune_dirs(os.path.dirname(dest))
        return

    if not quiet:
        log.info(u'Finished encoding {0}'.format(
            util.displayable_path(source)))
Exemplo n.º 9
0
    def move_art(self, copy=False, link=False):
        """Move or copy any existing album art so that it remains in the
        same directory as the items.
        """
        old_art = self.artpath
        if not old_art:
            return

        new_art = self.art_destination(old_art)
        if new_art == old_art:
            return

        new_art = util.unique_path(new_art)
        log.debug(u'moving album art {0} to {1}',
                  util.displayable_path(old_art),
                  util.displayable_path(new_art))
        if copy:
            util.copy(old_art, new_art)
        elif link:
            util.link(old_art, new_art)
        else:
            util.move(old_art, new_art)
        self.artpath = new_art

        # Prune old path when moving.
        if not copy:
            util.prune_dirs(os.path.dirname(old_art), self._db.directory)
Exemplo n.º 10
0
def manipulate_files(session):
    """A coroutine (pipeline stage) that performs necessary file
    manipulations *after* items have been added to the library.
    """
    task = None
    while True:
        task = yield task
        if task.should_skip():
            continue

        # Remove duplicate files marked for deletion.
        if task.remove_duplicates:
            for duplicate_path in task.duplicate_paths:
                log.debug(u'deleting replaced duplicate %s' %
                          util.displayable_path(duplicate_path))
                util.remove(duplicate_path)
                util.prune_dirs(os.path.dirname(duplicate_path),
                                session.lib.directory)

        # Move/copy/write files.
        items = task.imported_items()
        # Save the original paths of all items for deletion and pruning
        # in the next step (finalization).
        task.old_paths = [item.path for item in items]
        for item in items:
            if config['import']['move']:
                # Just move the file.
                item.move(False)
            elif config['import']['copy']:
                # If it's a reimport, move in-library files and copy
                # out-of-library files. Otherwise, copy and keep track
                # of the old path.
                old_path = item.path
                if task.replaced_items[item]:
                    # This is a reimport. Move in-library files and copy
                    # out-of-library files.
                    if session.lib.directory in util.ancestry(old_path):
                        item.move(False)
                        # We moved the item, so remove the
                        # now-nonexistent file from old_paths.
                        task.old_paths.remove(old_path)
                    else:
                        item.move(True)
                else:
                    # A normal import. Just copy files and keep track of
                    # old paths.
                    item.move(True)

            if config['import']['write'] and task.should_write_tags():
                item.write()

        # Save new paths.
        with session.lib.transaction():
            for item in items:
                item.store()

        # Plugin event.
        plugins.send('import_task_files', session=session, task=task)
Exemplo n.º 11
0
def manipulate_files(session):
    """A coroutine (pipeline stage) that performs necessary file
    manipulations *after* items have been added to the library.
    """
    task = None
    while True:
        task = yield task
        if task.should_skip():
            continue

        # Remove duplicate files marked for deletion.
        if task.remove_duplicates:
            for duplicate_path in task.duplicate_paths:
                log.debug(u'deleting replaced duplicate %s' %
                          util.displayable_path(duplicate_path))
                util.remove(duplicate_path)
                util.prune_dirs(os.path.dirname(duplicate_path),
                                session.lib.directory)

        # Move/copy/write files.
        items = task.imported_items()
        # Save the original paths of all items for deletion and pruning
        # in the next step (finalization).
        task.old_paths = [item.path for item in items]
        for item in items:
            if config['import']['move']:
                # Just move the file.
                item.move(False)
            elif config['import']['copy']:
                # If it's a reimport, move in-library files and copy
                # out-of-library files. Otherwise, copy and keep track
                # of the old path.
                old_path = item.path
                if task.replaced_items[item]:
                    # This is a reimport. Move in-library files and copy
                    # out-of-library files.
                    if session.lib.directory in util.ancestry(old_path):
                        item.move(False)
                        # We moved the item, so remove the
                        # now-nonexistent file from old_paths.
                        task.old_paths.remove(old_path)
                    else:
                        item.move(True)
                else:
                    # A normal import. Just copy files and keep track of
                    # old paths.
                    item.move(True)

            if config['import']['write'] and task.should_write_tags():
                item.try_write()

        # Save new paths.
        with session.lib.transaction():
            for item in items:
                item.store()

        # Plugin event.
        plugins.send('import_task_files', session=session, task=task)
Exemplo n.º 12
0
 def remove_duplicates(self, lib):
     duplicate_items = self.duplicate_items(lib)
     log.debug("removing %i old duplicated items" % len(duplicate_items))
     for item in duplicate_items:
         item.remove()
         if lib.directory in util.ancestry(item.path):
             log.debug(u"deleting duplicate %s" % util.displayable_path(item.path))
             util.remove(item.path)
             util.prune_dirs(os.path.dirname(item.path), lib.directory)
Exemplo n.º 13
0
 def prune(self, filename):
     """Prune any empty directories above the given file, which must
     not exist. If this task has no `toppath` or the file path
     provided is not within the `toppath`, then this function has no
     effect.
     """
     assert not os.path.exists(filename)
     if self.toppath:
         util.prune_dirs(os.path.dirname(filename), self.toppath)
Exemplo n.º 14
0
 def prune(self, filename):
     """Prune any empty directories above the given file. If this
     task has no `toppath` or the file path provided is not within
     the `toppath`, then this function has no effect. Similarly, if
     the file still exists, no pruning is performed, so it's safe to
     call when the file in question may not have been removed.
     """
     if self.toppath and not os.path.exists(filename):
         util.prune_dirs(os.path.dirname(filename), self.toppath, clutter=config["clutter"].as_str_seq())
Exemplo n.º 15
0
 def prune(self, filename):
     """Prune any empty directories above the given file. If this
     task has no `toppath` or the file path provided is not within
     the `toppath`, then this function has no effect. Similarly, if
     the file still exists, no pruning is performed, so it's safe to
     call when the file in question may not have been removed.
     """
     if self.toppath and not os.path.exists(filename):
         util.prune_dirs(os.path.dirname(filename), self.toppath)
Exemplo n.º 16
0
    def encode(self, command, source, dest, pretend=False):
        """Encode `source` to `dest` using command template `command`.

        Raises `subprocess.CalledProcessError` if the command exited with a
        non-zero status code.
        """
        # The paths and arguments must be bytes.
        assert isinstance(command, bytes)
        assert isinstance(source, bytes)
        assert isinstance(dest, bytes)

        quiet = self.config['quiet'].get(bool)

        if not quiet and not pretend:
            self._log.info('Encoding {0}', util.displayable_path(source))

        command = command.decode(arg_encoding(), 'surrogateescape')
        source = decode_commandline_path(source)
        dest = decode_commandline_path(dest)

        # Substitute $source and $dest in the argument list.
        args = shlex.split(command)
        encode_cmd = []
        for i, arg in enumerate(args):
            args[i] = Template(arg).safe_substitute({
                'source': source,
                'dest': dest,
            })
            encode_cmd.append(args[i].encode(util.arg_encoding()))

        if pretend:
            self._log.info('{0}', ' '.join(ui.decargs(args)))
            return

        try:
            util.command_output(encode_cmd)
        except subprocess.CalledProcessError as exc:
            # Something went wrong (probably Ctrl+C), remove temporary files
            self._log.info('Encoding {0} failed. Cleaning up...',
                           util.displayable_path(source))
            self._log.debug('Command {0} exited with status {1}: {2}',
                            args,
                            exc.returncode,
                            exc.output)
            util.remove(dest)
            util.prune_dirs(os.path.dirname(dest))
            raise
        except OSError as exc:
            raise ui.UserError(
                "convert: couldn't invoke '{}': {}".format(
                    ' '.join(ui.decargs(args)), exc
                )
            )

        if not quiet and not pretend:
            self._log.info('Finished encoding {0}',
                           util.displayable_path(source))
Exemplo n.º 17
0
 def remove_duplicates(self, lib):
     duplicate_items = self.duplicate_items(lib)
     log.debug('removing %i old duplicated items' % len(duplicate_items))
     for item in duplicate_items:
         item.remove()
         if lib.directory in util.ancestry(item.path):
             log.debug(u'deleting duplicate %s' %
                       util.displayable_path(item.path))
             util.remove(item.path)
             util.prune_dirs(os.path.dirname(item.path), lib.directory)
Exemplo n.º 18
0
 def remove_duplicates(self, lib):
     duplicate_items = self.duplicate_items(lib)
     log.debug(u'removing {0} old duplicated items'
               .format(len(duplicate_items)))
     for item in duplicate_items:
         item.remove()
         if lib.directory in util.ancestry(item.path):
             log.debug(u'deleting duplicate {0}'
                       .format(util.displayable_path(item.path)))
             util.remove(item.path)
             util.prune_dirs(os.path.dirname(item.path),
                             lib.directory)
Exemplo n.º 19
0
def encode(source, dest):
    log.info(u'Started encoding {0}'.format(util.displayable_path(source)))

    encode = Popen([conf['ffmpeg']] + ['-i', source] + conf['opts'] +
                   [dest], close_fds=True, stderr=DEVNULL)
    encode.wait()
    if encode.returncode != 0:
        # Something went wrong (probably Ctrl+C), remove temporary files
        log.info(u'Encoding {0} failed. Cleaning up...'.format(source))
        util.remove(dest)
        util.prune_dirs(os.path.dirname(dest))
        return
    log.info(u'Finished encoding {0}'.format(util.displayable_path(source)))
Exemplo n.º 20
0
    def encode(self, command, source, dest, pretend=False):
        """Encode `source` to `dest` using command template `command`.

        Raises `subprocess.CalledProcessError` if the command exited with a
        non-zero status code.
        """
        # The paths and arguments must be bytes.
        assert isinstance(command, bytes)
        assert isinstance(source, bytes)
        assert isinstance(dest, bytes)

        quiet = self.config['quiet'].get(bool)

        if not quiet and not pretend:
            self._log.info(u'Encoding {0}', util.displayable_path(source))

        # Substitute $source and $dest in the argument list.
        args = shlex.split(command)
        for i, arg in enumerate(args):
            args[i] = Template(arg).safe_substitute({
                'source': source,
                'dest': dest,
            })

        if pretend:
            self._log.info(u' '.join(ui.decargs(args)))
            return

        try:
            util.command_output(args)
        except subprocess.CalledProcessError as exc:
            # Something went wrong (probably Ctrl+C), remove temporary files
            self._log.info(u'Encoding {0} failed. Cleaning up...',
                           util.displayable_path(source))
            self._log.debug(u'Command {0} exited with status {1}: {2}',
                            args,
                            exc.returncode,
                            exc.output)
            util.remove(dest)
            util.prune_dirs(os.path.dirname(dest))
            raise
        except OSError as exc:
            raise ui.UserError(
                u"convert: couldn't invoke '{0}': {1}".format(
                    u' '.join(ui.decargs(args)), exc
                )
            )

        if not quiet and not pretend:
            self._log.info(u'Finished encoding {0}',
                           util.displayable_path(source))
Exemplo n.º 21
0
    def encode(self, command, source, dest, pretend=False):
        """Encode `source` to `dest` using command template `command`.

        Raises `subprocess.CalledProcessError` if the command exited with a
        non-zero status code.
        """
        # The paths and arguments must be bytes.
        assert isinstance(command, bytes)
        assert isinstance(source, bytes)
        assert isinstance(dest, bytes)

        quiet = self.config['quiet'].get(bool)

        if not quiet and not pretend:
            self._log.info(u'Encoding {0}', util.displayable_path(source))

        # Substitute $source and $dest in the argument list.
        args = shlex.split(command)
        for i, arg in enumerate(args):
            args[i] = Template(arg).safe_substitute({
                b'source': source,
                b'dest': dest,
            })

        if pretend:
            self._log.info(' '.join(args))
            return

        try:
            util.command_output(args)
        except subprocess.CalledProcessError as exc:
            # Something went wrong (probably Ctrl+C), remove temporary files
            self._log.info(u'Encoding {0} failed. Cleaning up...',
                           util.displayable_path(source))
            self._log.debug(u'Command {0} exited with status {1}',
                            exc.cmd.decode('utf8', 'ignore'),
                            exc.returncode)
            util.remove(dest)
            util.prune_dirs(os.path.dirname(dest))
            raise
        except OSError as exc:
            raise ui.UserError(
                u"convert: could invoke '{0}': {1}".format(
                    ' '.join(args), exc
                )
            )

        if not quiet and not pretend:
            self._log.info(u'Finished encoding {0}',
                           util.displayable_path(source))
Exemplo n.º 22
0
def encode(source, dest):
    log.info(u'Started encoding {0}'.format(util.displayable_path(source)))

    encode = Popen([conf['ffmpeg']] + ['-i', source] + conf['opts'] + [dest],
                   close_fds=True,
                   stderr=DEVNULL)
    encode.wait()
    if encode.returncode != 0:
        # Something went wrong (probably Ctrl+C), remove temporary files
        log.info(u'Encoding {0} failed. Cleaning up...'.format(source))
        util.remove(dest)
        util.prune_dirs(os.path.dirname(dest))
        return
    log.info(u'Finished encoding {0}'.format(util.displayable_path(source)))
Exemplo n.º 23
0
    def move(self,
             item,
             copy=False,
             in_album=False,
             basedir=None,
             with_album=True):
        """Move the item to its designated location within the library
        directory (provided by destination()). Subdirectories are
        created as needed. If the operation succeeds, the item's path
        field is updated to reflect the new location.
        
        If copy is True, moving the file is copied rather than moved.
        
        If in_album is True, then the track is treated as part of an
        album even if it does not yet have an album_id associated with
        it. (This allows items to be moved before they are added to the
        database, a performance optimization.)

        basedir overrides the library base directory for the
        destination.

        If the item is in an album, the album is given an opportunity to
        move its art. (This can be disabled by passing
        with_album=False.)
        
        The item is stored to the database if it is in the database, so
        any dirty fields prior to the move() call will be written as a
        side effect. You probably want to call save() to commit the DB
        transaction.
        """
        dest = self.destination(item, in_album=in_album, basedir=basedir)

        # Create necessary ancestry for the move.
        util.mkdirall(dest)

        # Perform the move and store the change.
        old_path = item.path
        item.move(dest, copy)
        if item.id is not None:
            self.store(item)

        # If this item is in an album, move its art.
        if with_album:
            album = self.get_album(item)
            if album:
                album.move_art(copy)

        # Prune vacated directory.
        if not copy:
            util.prune_dirs(os.path.dirname(old_path), self.directory)
Exemplo n.º 24
0
    def update(self, create=None):
        if (not os.path.isdir(syspath(self.directory))
                and not self.ask_create(create)):
            print_(u'Skipping creation of {0}'
                   .format(displayable_path(self.directory)))
            return

        converter = self.converter()
        for (item, actions) in self.items_actions():
            dest = self.destination(item)
            path = self.get_path(item)
            for action in actions:
                if action == self.MOVE:
                    print_(u'>{0} -> {1}'.format(displayable_path(path),
                                                 displayable_path(dest)))
                    util.mkdirall(dest)
                    util.move(path, dest)
                    util.prune_dirs(os.path.dirname(path), root=self.directory)
                    self.set_path(item, dest)
                    item.store()
                    path = dest
                elif action == self.WRITE:
                    print_(u'*{0}'.format(displayable_path(path)))
                    item.write(path=path)
                elif action == self.SYNC_ART:
                    print_(u'~{0}'.format(displayable_path(path)))
                    self.sync_art(item, path)
                elif action == self.ADD:
                    print_(u'+{0}'.format(displayable_path(dest)))
                    converter.submit(item)
                elif action == self.REMOVE:
                    print_(u'-{0}'.format(displayable_path(path)))
                    self.remove_item(item)
                    item.store()

        for item, dest in converter.as_completed():
            self.set_path(item, dest)
            item.store()
        converter.shutdown()

        for (album, actions) in self.albums_actions():
            for action in actions:
                dest_dir = self.album_destination(album)
                if action == self.COPY_ART:
                    path = album.artpath
                    dest = album.art_destination(path, dest_dir)
                    util.copy(path, dest, replace=True)
                    print_(u'$~{0}'.format(displayable_path(dest)))
                    print_(u'$!{0}'.format(displayable_path(path)))
Exemplo n.º 25
0
def encode(command, source, dest, pretend=False):
    """Encode `source` to `dest` using command template `command`.

    Raises `subprocess.CalledProcessError` if the command exited with a
    non-zero status code.
    """
    quiet = config['convert']['quiet'].get()

    if not quiet and not pretend:
        log.info(u'Encoding {0}'.format(util.displayable_path(source)))

    # Substitute $source and $dest in the argument list.
    args = shlex.split(command)
    for i, arg in enumerate(args):
        args[i] = Template(arg).safe_substitute({
            'source': source,
            'dest': dest,
        })

    if pretend:
        log.info(' '.join(args))
        return

    try:
        util.command_output(args)
    except subprocess.CalledProcessError as exc:
        # Something went wrong (probably Ctrl+C), remove temporary files
        log.info(u'Encoding {0} failed. Cleaning up...'
                 .format(util.displayable_path(source)))
        log.debug(u'Command {0} exited with status {1}'.format(
            exc.cmd.decode('utf8', 'ignore'),
            exc.returncode,
        ))
        util.remove(dest)
        util.prune_dirs(os.path.dirname(dest))
        raise
    except OSError as exc:
        raise ui.UserError(
            u"convert: could invoke '{0}': {1}".format(
                ' '.join(args), exc
            )
        )

    if not quiet and not pretend:
        log.info(u'Finished encoding {0}'.format(
            util.displayable_path(source))
        )
Exemplo n.º 26
0
def encode(command, source, dest, pretend=False):
    """Encode `source` to `dest` using command template `command`.

    Raises `subprocess.CalledProcessError` if the command exited with a
    non-zero status code.
    """
    quiet = config['convert']['quiet'].get()

    if not quiet and not pretend:
        log.info(u'Encoding {0}'.format(util.displayable_path(source)))

    if os.name == 'nt':
		command = Template(command).safe_substitute({
			'source': '"' + source + '"',
			'dest':   '"' + dest + '"',
		})
	else:
		command = Template(command).safe_substitute({
			'source': pipes.quote(source),
			'dest':   pipes.quote(dest),
		})

    log.debug(u'convert: executing: {0}'
              .format(util.displayable_path(command)))

    if pretend:
        log.info(command)
        return

    try:
        util.command_output(command, shell=True)
    except subprocess.CalledProcessError:
        # Something went wrong (probably Ctrl+C), remove temporary files
        log.info(u'Encoding {0} failed. Cleaning up...'
                 .format(util.displayable_path(source)))
        util.remove(dest)
        util.prune_dirs(os.path.dirname(dest))
        raise
    except OSError as exc:
        raise ui.UserError(
            u"convert: could invoke '{0}': {0}".format(command, exc)
        )

    if not quiet and not pretend:
        log.info(u'Finished encoding {0}'.format(
            util.displayable_path(source))
        )
Exemplo n.º 27
0
    def move(self, item, copy=False, in_album=False, basedir=None,
             with_album=True):
        """Move the item to its designated location within the library
        directory (provided by destination()). Subdirectories are
        created as needed. If the operation succeeds, the item's path
        field is updated to reflect the new location.
        
        If copy is True, moving the file is copied rather than moved.
        
        If in_album is True, then the track is treated as part of an
        album even if it does not yet have an album_id associated with
        it. (This allows items to be moved before they are added to the
        database, a performance optimization.)

        basedir overrides the library base directory for the
        destination.

        If the item is in an album, the album is given an opportunity to
        move its art. (This can be disabled by passing
        with_album=False.)
        
        The item is stored to the database if it is in the database, so
        any dirty fields prior to the move() call will be written as a
        side effect. You probably want to call save() to commit the DB
        transaction.
        """
        dest = self.destination(item, in_album=in_album, basedir=basedir)
        
        # Create necessary ancestry for the move.
        util.mkdirall(dest)
        
        # Perform the move and store the change.
        old_path = item.path
        item.move(dest, copy)
        if item.id is not None:
            self.store(item)

        # If this item is in an album, move its art.
        if with_album:
            album = self.get_album(item)
            if album:
                album.move_art(copy)

        # Prune vacated directory.
        if not copy:
            util.prune_dirs(os.path.dirname(old_path), self.directory)
Exemplo n.º 28
0
def finalize(config):
    """A coroutine that finishes up importer tasks. In particular, the
    coroutine sends plugin events, deletes old files, and saves
    progress. This is a "terminal" coroutine (it yields None).
    """
    lib = _reopen_lib(config.lib)
    while True:
        task = yield
        if task.should_skip():
            if config.resume is not False:
                task.save_progress()
            if config.incremental:
                task.save_history()
            continue

        items = [i for i in task.items if i] if task.is_album else [task.item]

        # Announce that we've added an album.
        if task.is_album:
            album = lib.get_album(task.album_id)
            plugins.send('album_imported', lib=lib, album=album, config=config)
        else:
            for item in items:
                plugins.send('item_imported',
                             lib=lib,
                             item=item,
                             config=config)

        # Finally, delete old files.
        if config.copy and config.delete:
            new_paths = [os.path.realpath(item.path) for item in items]
            for old_path in task.old_paths:
                # Only delete files that were actually copied.
                if old_path not in new_paths:
                    os.remove(syspath(old_path))
                    # Clean up directory if it is emptied.
                    if task.toppath:
                        util.prune_dirs(os.path.dirname(old_path),
                                        task.toppath)

        # Update progress.
        if config.resume is not False:
            task.save_progress()
        if config.incremental:
            task.save_history()
Exemplo n.º 29
0
    def move(self, copy=False, basedir=None, with_album=True, pretend=False):
        """Move the item to its designated location within the library
        directory (provided by destination()). Subdirectories are
        created as needed. If the operation succeeds, the item's path
        field is updated to reflect the new location.

        If copy is True, moving the file is copied rather than moved.

        basedir overrides the library base directory for the
        destination.

        If the item is in an album, the album is given an opportunity to
        move its art. (This can be disabled by passing
        with_album=False.)

        The item is stored to the database if it is in the database, so
        any dirty fields prior to the move() call will be written as a
        side effect. You probably want to call save() to commit the DB
        transaction.
        """
        self._check_db()
        dest = self.destination(basedir=basedir)
        if pretend:
            return dest

        # Create necessary ancestry for the move.
        util.mkdirall(dest)

        # Perform the move and store the change.
        old_path = self.path
        self.move_file(dest, copy)
        self.store()

        # If this item is in an album, move its art.
        if with_album:
            album = self.get_album()
            if album:
                album.move_art(copy)
                album.store()

        # Prune vacated directory.
        if not copy:
            util.prune_dirs(os.path.dirname(old_path), self._db.directory)
Exemplo n.º 30
0
def encode(source, dest):
    """Encode ``source`` to ``dest`` using the command from ``get_format()``.

    Raises an ``ui.UserError`` if the command was not found and a
    ``subprocess.CalledProcessError`` if the command exited with a
    non-zero status code.
    """
    quiet = config['convert']['quiet'].get()

    if not quiet:
        log.info(u'Started encoding {0}'.format(util.displayable_path(source)))

    command, _ = get_format()
    opts = []
    for arg in command:
        opts.append(Template(arg).safe_substitute({
            'source': source,
            'dest':   dest,
        }))

    log.debug(u'convert: executing: {0}'.format(
        u' '.join(pipes.quote(o.decode('utf8', 'ignore')) for o in opts)
    ))

    try:
        util.command_output(opts)
    except subprocess.CalledProcessError:
        # Something went wrong (probably Ctrl+C), remove temporary files
        log.info(u'Encoding {0} failed. Cleaning up...'
                 .format(util.displayable_path(source)))
        util.remove(dest)
        util.prune_dirs(os.path.dirname(dest))
        raise
    except OSError as exc:
        raise ui.UserError(
            u'convert: could invoke ffmpeg: {0}'.format(exc)
        )

    if not quiet:
        log.info(u'Finished encoding {0}'.format(
            util.displayable_path(source))
        )
Exemplo n.º 31
0
def finalize(config):
    """A coroutine that finishes up importer tasks. In particular, the
    coroutine sends plugin events, deletes old files, and saves
    progress. This is a "terminal" coroutine (it yields None).
    """
    lib = _reopen_lib(config.lib)
    while True:
        task = yield
        if task.should_skip():
            if config.resume is not False:
                task.save_progress()
            if config.incremental:
                task.save_history()
            continue

        items = [i for i in task.items if i] if task.is_album else [task.item]

        # Announce that we've added an album.
        if task.is_album:
            album = lib.get_album(task.album_id)
            plugins.send('album_imported', lib=lib, album=album, config=config)
        else:
            for item in items:
                plugins.send('item_imported', lib=lib, item=item, config=config)

        # Finally, delete old files.
        if config.copy and config.delete:
            new_paths = [os.path.realpath(item.path) for item in items]
            for old_path in task.old_paths:
                # Only delete files that were actually copied.
                if old_path not in new_paths:
                    os.remove(syspath(old_path))
                    # Clean up directory if it is emptied.
                    if task.toppath:
                        util.prune_dirs(os.path.dirname(old_path),
                                        task.toppath)

        # Update progress.
        if config.resume is not False:
            task.save_progress()
        if config.incremental:
            task.save_history()
Exemplo n.º 32
0
    def remove(self, item, delete=False, with_album=True):
        """Removes this item. If delete, then the associated file is
        removed from disk. If with_album, then the item's album (if any)
        is removed if it the item was the last in the album.
        """
        album = self.get_album(item) if with_album else None

        self.conn.execute('DELETE FROM items WHERE id=?', (item.id,))

        if album:
            item_iter = album.items()
            try:
                item_iter.next()
            except StopIteration:
                # Album is empty.
                album.remove(delete, False)

        if delete:
            util.soft_remove(item.path)
            util.prune_dirs(os.path.dirname(item.path), self.directory)
Exemplo n.º 33
0
    def remove(self, item, delete=False, with_album=True):
        """Removes this item. If delete, then the associated file is
        removed from disk. If with_album, then the item's album (if any)
        is removed if it the item was the last in the album.
        """
        album = self.get_album(item) if with_album else None

        self.conn.execute('DELETE FROM items WHERE id=?', (item.id,))

        if album:
            item_iter = album.items()
            try:
                item_iter.next()
            except StopIteration:
                # Album is empty.
                album.remove(delete, False)

        if delete:
            util.soft_remove(item.path)
            util.prune_dirs(os.path.dirname(item.path), self.directory)
Exemplo n.º 34
0
    def move(self, library, copy=False, in_album=False, basedir=None):
        """Move the item to its designated location within the library
        directory (provided by destination()). Subdirectories are
        created as needed. If the operation succeeds, the item's path
        field is updated to reflect the new location.
        
        If copy is True, moving the file is copied rather than moved.
        
        If in_album is True, then the track is treated as part of an
        album even if it does not yet have an album_id associated with
        it. (This allows items to be moved before they are added to the
        database, a performance optimization.)

        basedir overrides the library base directory for the
        destination.

        Passes on appropriate exceptions if directories cannot be
        created or moving/copying fails.
        
        Note that one should almost certainly call store() and
        library.save() after this method in order to keep on-disk data
        consistent.
        """
        dest = library.destination(self, in_album=in_album, basedir=basedir)
        
        # Create necessary ancestry for the move.
        util.mkdirall(dest)
        
        if not samefile(self.path, dest):
            if copy:
                util.copy(self.path, dest)
            else:
                util.move(self.path, dest)
            
        # Either copying or moving succeeded, so update the stored path.
        old_path = self.path
        self.path = dest

        # Prune vacated directory.
        if not copy:
            util.prune_dirs(os.path.dirname(old_path), library.directory)
Exemplo n.º 35
0
def encode(command, source, dest, pretend=False):
    """Encode `source` to `dest` using command template `command`.

    Raises `subprocess.CalledProcessError` if the command exited with a
    non-zero status code.
    """
    quiet = config['convert']['quiet'].get()

    if not quiet and not pretend:
        log.info(u'Encoding {0}'.format(util.displayable_path(source)))

    command = Template(command).safe_substitute({
        'source': pipes.quote(source),
        'dest':   pipes.quote(dest),
    })

    log.debug(u'convert: executing: {0}'
              .format(util.displayable_path(command)))

    if pretend:
        log.info(command)
        return

    try:
        util.command_output(command, shell=True)
    except subprocess.CalledProcessError:
        # Something went wrong (probably Ctrl+C), remove temporary files
        log.info(u'Encoding {0} failed. Cleaning up...'
                 .format(util.displayable_path(source)))
        util.remove(dest)
        util.prune_dirs(os.path.dirname(dest))
        raise
    except OSError as exc:
        raise ui.UserError(
            u"convert: could invoke '{0}': {0}".format(command, exc)
        )

    if not quiet and not pretend:
        log.info(u'Finished encoding {0}'.format(
            util.displayable_path(source))
        )
Exemplo n.º 36
0
def handle_cli_exit(lib, **_kwargs):
    for src_dir, dst_dir in directories_moved.iteritems():
        if dst_dir is MULTIPLE_DESTS:
            print ("moves out of %s have multiple dests, will not moveall" %
                   (src_dir,))
            continue
        remaining_items = lib.items(library.PathQuery('path', src_dir))
        if next(iter(remaining_items), None):
            print "some Beets items left in %s, will not moveall" % (src_dir,)
        elif os.path.exists(src_dir):
            print "moving all leftovers in %s to %s" % (src_dir, dst_dir)
            for dirent in os.listdir(src_dir):
                dirent_path = os.path.join(src_dir, dirent)
                try:
                    shutil.move(dirent_path, dst_dir)
                except shutil.Error, ex:
                    # Log it but move on.
                    # XXX unicode problem here if path has non-ascii
                    log.critical("failed to move {0} to {1}: {2}", dirent_path,
                                 dst_dir, ex)
            util.prune_dirs(src_dir)
Exemplo n.º 37
0
    def remove(self, delete=False, with_album=True):
        """Removes the item. If `delete`, then the associated file is
        removed from disk. If `with_album`, then the item's album (if
        any) is removed if it the item was the last in the album.
        """
        super(Item, self).remove()

        # Remove the album if it is empty.
        if with_album:
            album = self.get_album()
            if album and not album.items():
                album.remove(delete, False)

        # Send a 'item_removed' signal to plugins
        plugins.send('item_removed', item=self)

        # Delete the associated file.
        if delete:
            util.remove(self.path)
            util.prune_dirs(os.path.dirname(self.path), self._db.directory)

        self._db._memotable = {}
Exemplo n.º 38
0
    def move_art(self, copy=False):
        """Move or copy any existing album art so that it remains in the
        same directory as the items.
        """
        old_art = self.artpath
        if not old_art:
            return

        new_art = self.art_destination(old_art)
        if new_art == old_art:
            return

        log.debug('moving album art %s to %s' % (old_art, new_art))
        if copy:
            util.copy(old_art, new_art)
        else:
            util.move(old_art, new_art)
        self.artpath = new_art

        # Prune old path when moving.
        if not copy:
            util.prune_dirs(os.path.dirname(old_art), self._library.directory)
Exemplo n.º 39
0
    def convert(self, item):
        """Converts an item in an unsupported media format to ogg.  Config
        pending.
        This is stolen from Jakob Schnitzers convert plugin.
        """
        fd, dest = tempfile.mkstemp(u'.ogg')
        os.close(fd)
        source = item.path
        # FIXME: use avconv?
        command = u'ffmpeg -i $source -y -acodec libvorbis -vn -aq 2 $dest'.split(u' ')
        log.info(u'echonest: encoding {0} to {1}'
                .format(util.displayable_path(source),
                util.displayable_path(dest)))
        opts = []
        for arg in command:
            arg = arg.encode('utf-8')
            opts.append(Template(arg).substitute({
                'source':   source,
                'dest':     dest
            }))

        try:
            encode = Popen(opts, close_fds=True, stderr=DEVNULL)
            encode.wait()
        except Exception as exc:
            log.error(u'echonest: encode failed: {0}'.format(str(exc)))
            util.remove(dest)
            util.prune_dirs(os.path.dirname(dest))
            return None

        if encode.returncode != 0:
            log.info(u'echonest: encoding {0} failed ({1}). Cleaning up...'
                     .format(util.displayable_path(source), encode.returncode))
            util.remove(dest)
            util.prune_dirs(os.path.dirname(dest))
            return None
        log.info(u'echonest: finished encoding {0}'
                .format(util.displayable_path(source)))
        return dest
Exemplo n.º 40
0
    def remove(self, delete=False, with_album=True):
        """Removes the item. If `delete`, then the associated file is
        removed from disk. If `with_album`, then the item's album (if
        any) is removed if it the item was the last in the album.
        """
        super(Item, self).remove()

        # Remove the album if it is empty.
        if with_album:
            album = self.get_album()
            if album and not album.items():
                album.remove(delete, False)

        # Send a 'item_removed' signal to plugins
        plugins.send('item_removed', item=self)

        # Delete the associated file.
        if delete:
            util.remove(self.path)
            util.prune_dirs(os.path.dirname(self.path), self._db.directory)

        self._db._memotable = {}
Exemplo n.º 41
0
    def convert(self, item):
        """Converts an item in an unsupported media format to ogg.  Config
        pending.
        This is stolen from Jakob Schnitzers convert plugin.
        """
        fd, dest = tempfile.mkstemp(u'.ogg')
        os.close(fd)
        source = item.path
        # FIXME: use avconv?
        command = u'ffmpeg -i $source -y -acodec libvorbis -vn -aq 2 $dest'.split(u' ')
        log.info(u'echonest: encoding {0} to {1}'
                .format(util.displayable_path(source),
                util.displayable_path(dest)))
        opts = []
        for arg in command:
            arg = arg.encode('utf-8')
            opts.append(Template(arg).substitute({
                'source':   source,
                'dest':     dest
            }))

        try:
            encode = Popen(opts, close_fds=True, stderr=DEVNULL)
            encode.wait()
        except Exception as exc:
            log.error(u'echonest: encode failed: {0}'.format(str(exc)))
            util.remove(dest)
            util.prune_dirs(os.path.dirname(dest))
            return None

        if encode.returncode != 0:
            log.info(u'echonest: encoding {0} failed ({1}). Cleaning up...'
                     .format(util.displayable_path(source), encode.returncode))
            util.remove(dest)
            util.prune_dirs(os.path.dirname(dest))
            return None
        log.info(u'echonest: finished encoding {0}'
                .format(util.displayable_path(source)))
        return dest
Exemplo n.º 42
0
    def move_art(self, copy=False):
        """Move or copy any existing album art so that it remains in the
        same directory as the items.
        """
        old_art = self.artpath
        if not old_art:
            return

        new_art = self.art_destination(old_art)
        if new_art == old_art:
            return

        log.debug('moving album art %s to %s' % (old_art, new_art))
        if copy:
            util.copy(old_art, new_art)
        else:
            util.move(old_art, new_art)
        self.artpath = new_art

        # Prune old path when moving.
        if not copy: 
            util.prune_dirs(os.path.dirname(old_art),
                            self._library.directory)
Exemplo n.º 43
0
def encode(source, dest):
    command = get_command()
    quiet = config["convert"]["quiet"].get()
    opts = []

    if not quiet:
        log.info(u"Started encoding {0}".format(util.displayable_path(source)))

    for arg in command:
        arg = arg.encode("utf-8")
        opts.append(Template(arg).substitute({"source": source, "dest": dest}))

    encode = Popen(opts, close_fds=True, stderr=DEVNULL)
    encode.wait()

    if encode.returncode != 0:
        # Something went wrong (probably Ctrl+C), remove temporary files
        log.info(u"Encoding {0} failed. Cleaning up...".format(util.displayable_path(source)))
        util.remove(dest)
        util.prune_dirs(os.path.dirname(dest))
        return

    if not quiet:
        log.info(u"Finished encoding {0}".format(util.displayable_path(source)))
Exemplo n.º 44
0
def encode(source, dest):
    """Encode ``source`` to ``dest`` using the command from ``get_format()``.

    Raises an ``ui.UserError`` if the command was not found and a
    ``subprocess.CalledProcessError`` if the command exited with a
    non-zero status code.
    """
    quiet = config['convert']['quiet'].get()

    if not quiet:
        log.info(u'Started encoding {0}'.format(util.displayable_path(source)))

    command, _ = get_format()
    command = Template(command).safe_substitute({
        'source': pipes.quote(source),
        'dest': pipes.quote(dest),
    })

    log.debug(u'convert: executing: {0}'.format(
        util.displayable_path(command)))

    try:
        util.command_output(command, shell=True)
    except subprocess.CalledProcessError:
        # Something went wrong (probably Ctrl+C), remove temporary files
        log.info(u'Encoding {0} failed. Cleaning up...'.format(
            util.displayable_path(source)))
        util.remove(dest)
        util.prune_dirs(os.path.dirname(dest))
        raise
    except OSError as exc:
        raise ui.UserError(u'convert: could invoke ffmpeg: {0}'.format(exc))

    if not quiet:
        log.info(u'Finished encoding {0}'.format(
            util.displayable_path(source)))
Exemplo n.º 45
0
def apply_choices(session):
    """A coroutine for applying changes to albums and singletons during
    the autotag process.
    """
    task = None
    while True:
        task = yield task
        if task.should_skip():
            continue

        items = task.imported_items()
        # Clear IDs in case the items are being re-tagged.
        for item in items:
            item.id = None
            item.album_id = None

        # Change metadata.
        if task.should_write_tags():
            if task.is_album:
                autotag.apply_metadata(task.match.info, task.match.mapping)
            else:
                autotag.apply_item_metadata(task.item, task.match.info)
            plugins.send('import_task_apply', session=session, task=task)

        # Infer album-level fields.
        if task.is_album:
            _infer_album_fields(task)

        # Find existing item entries that these are replacing (for
        # re-imports). Old album structures are automatically cleaned up
        # when the last item is removed.
        task.replaced_items = defaultdict(list)
        for item in items:
            dup_items = session.lib.items(library.MatchQuery(
                'path', item.path))
            for dup_item in dup_items:
                task.replaced_items[item].append(dup_item)
                log.debug('replacing item %i: %s' %
                          (dup_item.id, displayable_path(item.path)))
        log.debug('%i of %i items replaced' %
                  (len(task.replaced_items), len(items)))

        # Find old items that should be replaced as part of a duplicate
        # resolution.
        duplicate_items = []
        if task.remove_duplicates:
            if task.is_album:
                for album in _duplicate_check(session.lib, task):
                    duplicate_items += album.items()
            else:
                duplicate_items = _item_duplicate_check(session.lib, task)
            log.debug('removing %i old duplicated items' %
                      len(duplicate_items))

            # Delete duplicate files that are located inside the library
            # directory.
            for duplicate_path in [i.path for i in duplicate_items]:
                if session.lib.directory in util.ancestry(duplicate_path):
                    log.debug(u'deleting replaced duplicate %s' %
                              util.displayable_path(duplicate_path))
                    util.remove(duplicate_path)
                    util.prune_dirs(os.path.dirname(duplicate_path),
                                    session.lib.directory)

        # Add items -- before path changes -- to the library. We add the
        # items now (rather than at the end) so that album structures
        # are in place before calls to destination().
        with session.lib.transaction():
            # Remove old items.
            for replaced in task.replaced_items.itervalues():
                for item in replaced:
                    session.lib.remove(item)
            for item in duplicate_items:
                session.lib.remove(item)

            # Add new ones.
            if task.is_album:
                # Add an album.
                album = session.lib.add_album(items)
                task.album_id = album.id
            else:
                # Add tracks.
                for item in items:
                    session.lib.add(item)
Exemplo n.º 46
0
 def remove_item(self, item):
     path = item[self.path_key].encode('utf8')
     util.remove(path)
     util.prune_dirs(path, root=self.directory)
     del item[self.path_key]
Exemplo n.º 47
0
def apply_choices(config):
    """A coroutine for applying changes to albums during the autotag
    process.
    """
    lib = _reopen_lib(config.lib)
    task = None
    while True:    
        task = yield task
        if task.should_skip():
            continue

        items = task.all_items()
        # Clear IDs in case the items are being re-tagged.
        for item in items:
            item.id = None
            item.album_id = None

        # Change metadata.
        if task.should_write_tags():
            if task.is_album:
                autotag.apply_metadata(task.items, task.info)
            else:
                autotag.apply_item_metadata(task.item, task.info)
            plugins.send('import_task_apply', config=config, task=task)

        # Infer album-level fields.
        if task.is_album:
            _infer_album_fields(task)

        # Find existing item entries that these are replacing (for
        # re-imports). Old album structures are automatically cleaned up
        # when the last item is removed.
        replaced_items = defaultdict(list)
        for item in items:
            dup_items = lib.items(library.MatchQuery('path', item.path))
            for dup_item in dup_items:
                replaced_items[item].append(dup_item)
                log.debug('replacing item %i: %s' %
                          (dup_item.id, displayable_path(item.path)))
        log.debug('%i of %i items replaced' % (len(replaced_items),
                                               len(items)))

        # Find old items that should be replaced as part of a duplicate
        # resolution.
        duplicate_items = []
        if task.remove_duplicates:
            if task.is_album:
                for album in _duplicate_check(lib, task):
                    duplicate_items += album.items()
            else:
                duplicate_items = _item_duplicate_check(lib, task)
            log.debug('removing %i old duplicated items' %
                      len(duplicate_items))

            # Delete duplicate files that are located inside the library
            # directory.
            for duplicate_path in [i.path for i in duplicate_items]:
                if lib.directory in util.ancestry(duplicate_path):
                    log.debug(u'deleting replaced duplicate %s' %
                              util.displayable_path(duplicate_path))
                    util.soft_remove(duplicate_path)
                    util.prune_dirs(os.path.dirname(duplicate_path),
                                    lib.directory)

        # Add items -- before path changes -- to the library. We add the
        # items now (rather than at the end) so that album structures
        # are in place before calls to destination().
        try:
            # Remove old items.
            for replaced in replaced_items.itervalues():
                for item in replaced:
                    lib.remove(item)
            for item in duplicate_items:
                lib.remove(item)

            # Add new ones.
            if task.is_album:
                # Add an album.
                album = lib.add_album(items)
                task.album_id = album.id
            else:
                # Add tracks.
                for item in items:
                    lib.add(item)
        finally:
            lib.save()

        # Move/copy files.
        task.old_paths = [item.path for item in items]  # For deletion.
        for item in items:
            if config.copy or config.move:
                if config.move:
                    # Just move the file.
                    lib.move(item, False)
                else:
                    # If it's a reimport, move the file. Otherwise, copy
                    # and keep track of the old path.
                    old_path = item.path
                    do_copy = not bool(replaced_items[item])
                    lib.move(item, do_copy)
                    if not do_copy:
                        # If we moved the item, remove the now-nonexistent
                        # file from old_paths.
                        task.old_paths.remove(old_path)

            if config.write and task.should_write_tags():
                item.write()

        # Save new paths.
        try:
            for item in items:
                lib.store(item)
        finally:
            lib.save()
Exemplo n.º 48
0
 def remove_item(self, item):
     path = self.get_path(item)
     _remove(path)
     util.prune_dirs(path, root=self.directory)
     del item[self.path_key]
Exemplo n.º 49
0
def apply_choices(session):
    """A coroutine for applying changes to albums and singletons during
    the autotag process.
    """
    task = None
    while True:
        task = yield task
        if task.should_skip():
            continue

        items = task.imported_items()
        # Clear IDs in case the items are being re-tagged.
        for item in items:
            item.id = None
            item.album_id = None

        # Change metadata.
        if task.should_write_tags():
            if task.is_album:
                autotag.apply_metadata(
                    task.match.info, task.match.mapping
                )
            else:
                autotag.apply_item_metadata(task.item, task.match.info)
            plugins.send('import_task_apply', session=session, task=task)

        # Infer album-level fields.
        if task.is_album:
            _infer_album_fields(task)

        # Find existing item entries that these are replacing (for
        # re-imports). Old album structures are automatically cleaned up
        # when the last item is removed.
        task.replaced_items = defaultdict(list)
        for item in items:
            dup_items = session.lib.items(library.MatchQuery('path', item.path))
            for dup_item in dup_items:
                task.replaced_items[item].append(dup_item)
                log.debug('replacing item %i: %s' %
                          (dup_item.id, displayable_path(item.path)))
        log.debug('%i of %i items replaced' % (len(task.replaced_items),
                                               len(items)))

        # Find old items that should be replaced as part of a duplicate
        # resolution.
        duplicate_items = []
        if task.remove_duplicates:
            if task.is_album:
                for album in _duplicate_check(session.lib, task):
                    duplicate_items += album.items()
            else:
                duplicate_items = _item_duplicate_check(session.lib, task)
            log.debug('removing %i old duplicated items' %
                      len(duplicate_items))

            # Delete duplicate files that are located inside the library
            # directory.
            for duplicate_path in [i.path for i in duplicate_items]:
                if session.lib.directory in util.ancestry(duplicate_path):
                    log.debug(u'deleting replaced duplicate %s' %
                              util.displayable_path(duplicate_path))
                    util.remove(duplicate_path)
                    util.prune_dirs(os.path.dirname(duplicate_path),
                                    session.lib.directory)

        # Add items -- before path changes -- to the library. We add the
        # items now (rather than at the end) so that album structures
        # are in place before calls to destination().
        with session.lib.transaction():
            # Remove old items.
            for replaced in task.replaced_items.itervalues():
                for item in replaced:
                    session.lib.remove(item)
            for item in duplicate_items:
                session.lib.remove(item)

            # Add new ones.
            if task.is_album:
                # Add an album.
                album = session.lib.add_album(items)
                task.album_id = album.id
            else:
                # Add tracks.
                for item in items:
                    session.lib.add(item)
Exemplo n.º 50
0
 def test_prune_nonexistent_directory(self):
     util.prune_dirs(os.path.join(self.sub, 'another'), self.base)
     self.assertExists(self.base)
     self.assertNotExists(self.sub)
Exemplo n.º 51
0
 def test_prune_existent_directory(self):
     util.prune_dirs(self.sub, self.base)
     self.assertExists(self.base)
     self.assertNotExists(self.sub)
Exemplo n.º 52
0
 def test_prune_nonexistent_directory(self):
     util.prune_dirs(os.path.join(self.sub, 'another'), self.base)
     self.assertExists(self.base)
     self.assertNotExists(self.sub)
Exemplo n.º 53
0
 def test_prune_existent_directory(self):
     util.prune_dirs(self.sub, self.base)
     self.assertExists(self.base)
     self.assertNotExists(self.sub)
Exemplo n.º 54
0
    def encode(self, command, source, dest, pretend=False):
        """Encode `source` to `dest` using command template `command`.

        Raises `subprocess.CalledProcessError` if the command exited with a
        non-zero status code.
        """
        # The paths and arguments must be bytes.
        assert isinstance(command, bytes)
        assert isinstance(source, bytes)
        assert isinstance(dest, bytes)

        quiet = self.config['quiet'].get(bool)

        if not quiet and not pretend:
            self._log.info(u'Encoding {0}', util.displayable_path(source))

        # On Python 3, we need to construct the command to invoke as a
        # Unicode string. On Unix, this is a little unfortunate---the OS is
        # expecting bytes---so we use surrogate escaping and decode with the
        # argument encoding, which is the same encoding that will then be
        # *reversed* to recover the same bytes before invoking the OS. On
        # Windows, we want to preserve the Unicode filename "as is."
        if not six.PY2:
            command = command.decode(util.arg_encoding(), 'surrogateescape')
            if platform.system() == 'Windows':
                source = source.decode(util._fsencoding())
                dest = dest.decode(util._fsencoding())
            else:
                source = source.decode(util.arg_encoding(), 'surrogateescape')
                dest = dest.decode(util.arg_encoding(), 'surrogateescape')

        # Substitute $source and $dest in the argument list.
        args = shlex.split(command)
        encode_cmd = []
        for i, arg in enumerate(args):
            args[i] = Template(arg).safe_substitute({
                'source': source,
                'dest': dest,
            })
            if six.PY2:
                encode_cmd.append(args[i])
            else:
                encode_cmd.append(args[i].encode(util.arg_encoding()))

        if pretend:
            self._log.info(u'{0}', u' '.join(ui.decargs(args)))
            return

        try:
            util.command_output(encode_cmd)
        except subprocess.CalledProcessError as exc:
            # Something went wrong (probably Ctrl+C), remove temporary files
            self._log.info(u'Encoding {0} failed. Cleaning up...',
                           util.displayable_path(source))
            self._log.debug(u'Command {0} exited with status {1}: {2}',
                            args,
                            exc.returncode,
                            exc.output)
            util.remove(dest)
            util.prune_dirs(os.path.dirname(dest))
            raise
        except OSError as exc:
            raise ui.UserError(
                u"convert: couldn't invoke '{0}': {1}".format(
                    u' '.join(ui.decargs(args)), exc
                )
            )

        if not quiet and not pretend:
            self._log.info(u'Finished encoding {0}',
                           util.displayable_path(source))