Пример #1
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

        log.info(u'echonest: encoding {0} to {1}'.format(
            util.displayable_path(source),
            util.displayable_path(dest),
        ))

        # Build up the FFmpeg command line.
        # FIXME: use avconv?
        command = u'ffmpeg -i $source -y -acodec libvorbis -vn -aq 2 $dest'
        opts = []
        for arg in command.split():
            arg = arg.encode('utf-8')
            opts.append(Template(arg).substitute(source=source, dest=dest))

        # Run the command.
        try:
            subprocess.check_call(opts, close_fds=True, stderr=DEVNULL)
        except (OSError, subprocess.CalledProcessError) as exc:
            log.debug(u'echonest: encode failed: {0}'.format(exc))
            util.remove(dest)
            return

        log.info(u'echonest: finished encoding {0}'.format(
            util.displayable_path(source))
        )
        return dest
Пример #2
0
    def set_art(self, path, copy=True):
        """Sets the album's cover art to the image at the given path.
        The image is copied (or moved) into place, replacing any
        existing art.
        """
        path = bytestring_path(path)
        oldart = self.artpath
        artdest = self.art_destination(path)

        if oldart and samefile(path, oldart):
            # Art already set.
            return
        elif samefile(path, artdest):
            # Art already in place.
            self.artpath = path
            return

        # Normal operation.
        if oldart == artdest:
            util.remove(oldart)
        artdest = util.unique_path(artdest)
        if copy:
            util.copy(path, artdest)
        else:
            util.move(path, artdest)
        self.artpath = artdest
Пример #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)
Пример #4
0
    def convert_on_import(self, lib, item):
        """Transcode a file automatically after it is imported into the
        library.
        """
        fmt = self.config['format'].as_str().lower()
        if should_transcode(item, fmt):
            command, ext = get_format()

            # Create a temporary file for the conversion.
            tmpdir = self.config['tmpdir'].get()
            if tmpdir:
                tmpdir = util.py3_path(util.bytestring_path(tmpdir))
            fd, dest = tempfile.mkstemp(util.py3_path(b'.' + ext), dir=tmpdir)
            os.close(fd)
            dest = util.bytestring_path(dest)
            _temp_files.append(dest)  # Delete the transcode later.

            # Convert.
            try:
                self.encode(command, item.path, dest)
            except subprocess.CalledProcessError:
                return

            # Change the newly-imported database entry to point to the
            # converted file.
            source_path = item.path
            item.path = dest
            item.write()
            item.read()  # Load new audio information data.
            item.store()

            if self.config['delete_originals']:
                self._log.info(u'Removing original file {0}', source_path)
                util.remove(source_path, False)
Пример #5
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))
        )
Пример #6
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)))
Пример #7
0
    def prepare_upload(self, item):
        """Truncate and convert an item's audio file so it can be
        uploaded to echonest.

        Return a ``(source, tmp)`` tuple where `source` is the path to
        the file to be uploaded and `tmp` is a temporary file to be
        deleted after the upload or `None`.

        If conversion or truncation fails, return `None`.
        """
        source = item.path
        tmp = None
        if item.format not in ALLOWED_FORMATS:
            if self.config['convert']:
                tmp = source = self.convert(source)
            if not tmp:
                return

        if os.stat(source).st_size > UPLOAD_MAX_SIZE:
            if self.config['truncate']:
                source = self.truncate(source)
                if tmp is not None:
                    util.remove(tmp)
                tmp = source
            else:
                return

        if source:
            return source, tmp
Пример #8
0
    def truncate(self, item):
        """Truncates an item to a size less than UPLOAD_MAX_SIZE."""
        fd, dest = tempfile.mkstemp(u'.ogg')
        os.close(fd)
        source = item.path

        log.info(u'echonest: truncating {0} to {1}'.format(
            util.displayable_path(source),
            util.displayable_path(dest),
        ))

        command = u'ffmpeg -t 300 -i $source -y -acodec copy $dest'
        opts = []
        for arg in command.split():
            arg = arg.encode('utf-8')
            opts.append(Template(arg).substitute(source=source, dest=dest))

        # Run the command.
        try:
            util.command_output(opts)
        except (OSError, subprocess.CalledProcessError) as exc:
            log.debug(u'echonest: truncate failed: {0}'.format(exc))
            util.remove(dest)
            return

        log.info(u'echonest: truncate encoding {0}'.format(
            util.displayable_path(source))
        )
        return dest
Пример #9
0
    def convert(self, source):
        """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)

        self._log.info(u'encoding {0} to {1}', util.displayable_path(source),
                       util.displayable_path(dest))

        opts = []
        for arg in CONVERT_COMMAND.split():
            arg = arg.encode('utf-8')
            opts.append(Template(arg).substitute(source=source, dest=dest))

        # Run the command.
        try:
            util.command_output(opts)
        except (OSError, subprocess.CalledProcessError) as exc:
            self._log.debug(u'encode failed: {0}', exc)
            util.remove(dest)
            return

        self._log.info(u'finished encoding {0}', util.displayable_path(source))
        return dest
Пример #10
0
    def prepare_upload(self, item):
        """Truncate and convert an item's audio file so it can be
        uploaded to echonest.

        Return a ``(source, tmp)`` tuple where `source` is the path to
        the file to be uploaded and `tmp` is a temporary file to be
        deleted after the upload or `None`.

        If conversion or truncation fails, return `None`.
        """
        source = item.path
        tmp = None
        if item.format not in ALLOWED_FORMATS:
            if self.config['convert']:
                tmp = source = self.convert(source)
            if not tmp:
                return

        if os.stat(source).st_size > UPLOAD_MAX_SIZE:
            if self.config['truncate']:
                source = self.truncate(source)
                if tmp is not None:
                    util.remove(tmp)
                tmp = source
            else:
                return

        if source:
            return source, tmp
Пример #11
0
    def test_add_nonexistent(self):
        item = self.add_external_track('myexternal')
        path = self.get_path(item)
        util.remove(path)

        self.runcli('alt', 'update', 'myexternal')
        self.assertIsFile(self.get_path(item))
Пример #12
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)))
Пример #13
0
    def convert(self, source):
        """Converts an item in an unsupported media format to ogg.  Config
        pending.
        This is stolen from Jakob Schnitzers convert plugin.
        """
        fd, dest = tempfile.mkstemp(b'.ogg')
        os.close(fd)

        self._log.info(u'encoding {0} to {1}',
                       util.displayable_path(source),
                       util.displayable_path(dest))

        opts = []
        for arg in CONVERT_COMMAND.split():
            arg = arg.encode('utf-8')
            opts.append(Template(arg).substitute(source=source, dest=dest))

        # Run the command.
        try:
            util.command_output(opts)
        except (OSError, subprocess.CalledProcessError) as exc:
            self._log.debug(u'encode failed: {0}', exc)
            util.remove(dest)
            return

        self._log.info(u'finished encoding {0}', util.displayable_path(source))
        return dest
Пример #14
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

        log.info(u'echonest: encoding {0} to {1}'.format(
            util.displayable_path(source),
            util.displayable_path(dest),
        ))

        # Build up the FFmpeg command line.
        # FIXME: use avconv?
        command = u'ffmpeg -i $source -y -acodec libvorbis -vn -aq 2 $dest'
        opts = []
        for arg in command.split():
            arg = arg.encode('utf-8')
            opts.append(Template(arg).substitute(source=source, dest=dest))

        # Run the command.
        try:
            subprocess.check_call(opts, close_fds=True, stderr=DEVNULL)
        except (OSError, subprocess.CalledProcessError) as exc:
            log.debug(u'echonest: encode failed: {0}'.format(exc))
            util.remove(dest)
            return

        log.info(u'echonest: finished encoding {0}'.format(
            util.displayable_path(source))
        )
        return dest
Пример #15
0
    def set_art(self, path, copy=True):
        """Sets the album's cover art to the image at the given path.
        The image is copied (or moved) into place, replacing any
        existing art.

        Sends an 'art_set' event with `self` as the sole argument.
        """
        path = bytestring_path(path)
        oldart = self.artpath
        artdest = self.art_destination(path)

        if oldart and samefile(path, oldart):
            # Art already set.
            return
        elif samefile(path, artdest):
            # Art already in place.
            self.artpath = path
            return

        # Normal operation.
        if oldart == artdest:
            util.remove(oldart)
        artdest = util.unique_path(artdest)
        if copy:
            util.copy(path, artdest)
        else:
            util.move(path, artdest)
        self.artpath = artdest

        plugins.send('art_set', album=self)
Пример #16
0
    def truncate(self, source):
        """Truncates an item to a size less than UPLOAD_MAX_SIZE."""
        fd, dest = tempfile.mkstemp(u'.ogg')
        os.close(fd)

        log.info(u'echonest: truncating {0} to {1}',
                 util.displayable_path(source),
                 util.displayable_path(dest))

        opts = []
        for arg in TRUNCATE_COMMAND.split():
            arg = arg.encode('utf-8')
            opts.append(Template(arg).substitute(source=source, dest=dest))

        # Run the command.
        try:
            util.command_output(opts)
        except (OSError, subprocess.CalledProcessError) as exc:
            log.debug(u'echonest: truncate failed: {0}', exc)
            util.remove(dest)
            return

        log.info(u'echonest: truncate encoding {0}',
                 util.displayable_path(source))
        return dest
Пример #17
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)
Пример #18
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)
Пример #19
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)
Пример #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('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))
Пример #21
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)
Пример #22
0
 def test_fetch_art_if_imported_file_deleted(self):
     # See #1126. Test the following scenario:
     #   - Album art imported, `album.artpath` set.
     #   - Imported album art file subsequently deleted (by user or other
     #     program).
     # `fetchart` should import album art again instead of printing the
     # message "<album> has album art".
     self._fetch_art(True)
     util.remove(self.album.artpath)
     self.plugin.batch_fetch_art(self.lib, self.lib.albums(), force=False)
     self.assertExists(self.album.artpath)
Пример #23
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)
Пример #24
0
    def analyze(self, item):
        """Upload the item to the EchoNest for analysis. May require to
        convert the item to a supported media format.
        """
        prepared = self.prepare_upload(item)
        if not prepared:
            self._log.debug(u'could not prepare file for upload')
            return

        source, tmp = prepared
        self._log.info(u'uploading file, please be patient')
        track = self._echofun(pyechonest.track.track_from_filename,
                              filename=source)
        if tmp is not None:
            util.remove(tmp)

        if not track:
            self._log.debug(u'failed to upload file')
            return

        # Sometimes we have a track but no song. I guess this happens for
        # new / unverified songs. We need to "extract" the audio_summary
        # from the track object manually.  I don't know why the
        # pyechonest API handles tracks (merge audio_summary to __dict__)
        # and songs (keep audio_summary in an extra attribute)
        # differently.
        # Maybe a patch for pyechonest could help?

        # First get the (limited) metadata from the track in case
        # there's no associated song.
        from_track = {}
        for key in ATTRIBUTES:
            try:
                from_track[key] = getattr(track, key)
            except AttributeError:
                pass
        from_track['duration'] = track.duration

        # Try to look up a song for the full metadata.
        try:
            song_id = track.song_id
        except AttributeError:
            return from_track
        songs = self._echofun(pyechonest.song.profile,
                              ids=[song_id],
                              track_ids=[track.id],
                              buckets=['audio_summary'])
        if songs:
            pick = self._pick_song(songs, item)
            if pick:
                return self._flatten_song(pick)
        return from_track  # Fall back to track metadata.
Пример #25
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)))
Пример #26
0
    def analyze(self, item):
        """Upload the item to the EchoNest for analysis. May require to
        convert the item to a supported media format.
        """
        prepared = self.prepare_upload(item)
        if not prepared:
            self._log.debug(u'could not prepare file for upload')
            return

        source, tmp = prepared
        self._log.info(u'uploading file, please be patient')
        track = self._echofun(pyechonest.track.track_from_filename,
                              filename=source)
        if tmp is not None:
            util.remove(tmp)

        if not track:
            self._log.debug(u'failed to upload file')
            return

        # Sometimes we have a track but no song. I guess this happens for
        # new / unverified songs. We need to "extract" the audio_summary
        # from the track object manually.  I don't know why the
        # pyechonest API handles tracks (merge audio_summary to __dict__)
        # and songs (keep audio_summary in an extra attribute)
        # differently.
        # Maybe a patch for pyechonest could help?

        # First get the (limited) metadata from the track in case
        # there's no associated song.
        from_track = {}
        for key in ATTRIBUTES:
            try:
                from_track[key] = getattr(track, key)
            except AttributeError:
                pass
        from_track['duration'] = track.duration

        # Try to look up a song for the full metadata.
        try:
            song_id = track.song_id
        except AttributeError:
            return from_track
        songs = self._echofun(pyechonest.song.profile,
                              ids=[song_id], track_ids=[track.id],
                              buckets=['audio_summary'])
        if songs:
            pick = self._pick_song(songs, item)
            if pick:
                return self._flatten_song(pick)
        return from_track  # Fall back to track metadata.
Пример #27
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))
Пример #28
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)))
Пример #29
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))
Пример #30
0
def finalize(session):
    """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).
    """
    while True:
        task = yield
        if task.should_skip():
            if _resume():
                task.save_progress()
            if config['import']['incremental']:
                task.save_history()
            task.cleanup()
            continue

        items = task.imported_items()

        # When copying and deleting originals, delete old files.
        if config['import']['copy'] and config['import']['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:
                    util.remove(syspath(old_path), False)
                    task.prune(old_path)

        # When moving, prune empty directories containing the original
        # files.
        elif config['import']['move']:
            for old_path in task.old_paths:
                task.prune(old_path)

        # Update progress.
        if _resume():
            task.save_progress()
        if config['import']['incremental']:
            task.save_history()
        task.cleanup()

        # Announce that we've added an album.
        if task.is_album:
            album = session.lib.get_album(task.album_id)
            plugins.send('album_imported',
                         lib=session.lib, album=album)
        else:
            for item in items:
                plugins.send('item_imported',
                             lib=session.lib, item=item)
Пример #31
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))
        )
Пример #32
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))
        )
Пример #33
0
def finalize(session):
    """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).
    """
    while True:
        task = yield
        if task.should_skip():
            if _resume():
                task.save_progress()
            if config['import']['incremental']:
                task.save_history()
            continue

        items = task.imported_items()

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

        # When copying and deleting originals, delete old files.
        if config['import']['copy'] and config['import']['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:
                    util.remove(syspath(old_path), False)
                    task.prune(old_path)

        # When moving, prune empty directories containing the original
        # files.
        elif config['import']['move']:
            for old_path in task.old_paths:
                task.prune(old_path)

        # Update progress.
        if _resume():
            task.save_progress()
        if config['import']['incremental']:
            task.save_history()
Пример #34
0
    def remove(self, delete=False, with_items=True):
        """Removes this album and all its associated items from the
        library. If delete, then the items' files are also deleted
        from disk, along with any album art. The directories
        containing the album are also removed (recursively) if empty.
        Set with_items to False to avoid removing the album's items.
        """
        super(Album, self).remove()

        # Delete art file.
        if delete:
            artpath = self.artpath
            if artpath:
                util.remove(artpath)

        # Remove (and possibly delete) the constituent items.
        if with_items:
            for item in self.items():
                item.remove(delete, False)
Пример #35
0
    def remove(self, delete=False, with_items=True):
        """Removes this album and all its associated items from the
        library. If delete, then the items' files are also deleted
        from disk, along with any album art. The directories
        containing the album are also removed (recursively) if empty.
        Set with_items to False to avoid removing the album's items.
        """
        super(Album, self).remove()

        # Delete art file.
        if delete:
            artpath = self.artpath
            if artpath:
                util.remove(artpath)

        # Remove (and possibly delete) the constituent items.
        if with_items:
            for item in self.items():
                item.remove(delete, False)
Пример #36
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))
        )
Пример #37
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 = task.all_items()

        # 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:
                    util.remove(syspath(old_path), False)
                    # Clean up directory if it is emptied.
                    if task.toppath:
                        task.prune(old_path)

        # Update progress.
        if config.resume is not False:
            task.save_progress()
        if config.incremental:
            task.save_history()
Пример #38
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))
        )
Пример #39
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).
    """
    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 = task.all_items()

        # Announce that we've added an album.
        if task.is_album:
            album = config.lib.get_album(task.album_id)
            plugins.send('album_imported',
                         lib=config.lib, album=album, config=config)
        else:
            for item in items:
                plugins.send('item_imported',
                             lib=config.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:
                    util.remove(syspath(old_path), False)
                    task.prune(old_path)

        # Update progress.
        if config.resume is not False:
            task.save_progress()
        if config.incremental:
            task.save_history()
Пример #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 = {}
Пример #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
    def _generate_playist(self):
        training_name = self._get_cleaned_training_name()
        playlist_name = self._get_training_name()
        target_name = common.get_training_attribute(self.training, "target")

        if not common.get_target_attribute_for_training(
                self.training, "generate_playlist"):
            common.say("Playlist generation to target[{0}] was skipped "
                       "(generate_playlist=no).".format(target_name),
                       log_only=False)
            return

        dst_path = Path(common.get_destination_path_for_training(
            self.training))
        dst_sub_dir = dst_path.joinpath(training_name)
        playlist_filename = "{}.m3u".format(playlist_name)
        dst = dst_sub_dir.joinpath(playlist_filename)

        lines = [
            "# Playlist generated for training '{}' on {}". \
                format(training_name, datetime.now())
        ]

        for item in self.items:
            path = util.displayable_path(
                item.get("exportpath", item.get("path")))
            if path:
                path = util.syspath(path)
                line = "{path}".format(path=path)
                lines.append(line)

        with tempfile.NamedTemporaryFile(mode='w+b', delete=False) as ntf:
            tmp_playlist = ntf.name
            for line in lines:
                ntf.write("{}\n".format(line).encode("utf-8"))

        common.say("Created playlist: {0}".format(dst), log_only=True)
        util.copy(tmp_playlist, dst)
        util.remove(tmp_playlist)
Пример #43
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 = {}
Пример #44
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
Пример #45
0
    def truncate(self, source):
        """Truncates an item to a size less than UPLOAD_MAX_SIZE."""
        fd, dest = tempfile.mkstemp(u'.ogg')
        os.close(fd)

        self._log.info(u'truncating {0} to {1}', util.displayable_path(source),
                       util.displayable_path(dest))

        opts = []
        for arg in TRUNCATE_COMMAND.split():
            arg = arg.encode('utf-8')
            opts.append(Template(arg).substitute(source=source, dest=dest))

        # Run the command.
        try:
            util.command_output(opts)
        except (OSError, subprocess.CalledProcessError) as exc:
            self._log.debug(u'truncate failed: {0}', exc)
            util.remove(dest)
            return

        self._log.info(u'truncate encoding {0}', util.displayable_path(source))
        return dest
Пример #46
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)))
Пример #47
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)))
Пример #48
0
    def cleanup(self, copy=False, delete=False, move=False):
        """Remove and prune imported paths.
        """
        # FIXME Maybe the keywords should be task properties.

        # FIXME This shouldn't be here. Skipping should be handled in
        # the stages.
        if self.skip:
            return
        items = self.imported_items()

        # When copying and deleting originals, delete old files.
        if copy and delete:
            new_paths = [os.path.realpath(item.path) for item in items]
            for old_path in self.old_paths:
                # Only delete files that were actually copied.
                if old_path not in new_paths:
                    util.remove(syspath(old_path), False)
                    self.prune(old_path)

        # When moving, prune empty directories containing the original files.
        elif move:
            for old_path in self.old_paths:
                self.prune(old_path)
Пример #49
0
    def cleanup(self, copy=False, delete=False, move=False):
        """Remove and prune imported paths.
        """
        # FIXME Maybe the keywords should be task properties.

        # FIXME This shouldn't be here. Skipping should be handled in
        # the stages.
        if self.skip:
            return
        items = self.imported_items()

        # When copying and deleting originals, delete old files.
        if copy and delete:
            new_paths = [os.path.realpath(item.path) for item in items]
            for old_path in self.old_paths:
                # Only delete files that were actually copied.
                if old_path not in new_paths:
                    util.remove(syspath(old_path), False)
                    self.prune(old_path)

        # When moving, prune empty directories containing the original files.
        elif move:
            for old_path in self.old_paths:
                self.prune(old_path)
Пример #50
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)
Пример #51
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))
Пример #52
0
    def play_music(self, lib, opts, args):
        """Execute query, create temporary playlist and execute player
        command passing that playlist, at request insert optional arguments.
        """
        command_str = config["play"]["command"].get()
        use_folders = config["play"]["use_folders"].get(bool)
        relative_to = config["play"]["relative_to"].get()
        raw = config["play"]["raw"].get(bool)
        if relative_to:
            relative_to = util.normpath(relative_to)

        # Add optional arguments to the player command.
        if opts.args:
            if ARGS_MARKER in command_str:
                command_str = command_str.replace(ARGS_MARKER, opts.args)
            else:
                command_str = "{} {}".format(command_str, opts.args)

        # Perform search by album and add folders rather than tracks to
        # playlist.
        if opts.album:
            selection = lib.albums(ui.decargs(args))
            paths = []

            sort = lib.get_default_album_sort()
            for album in selection:
                if use_folders:
                    paths.append(album.item_dir())
                else:
                    paths.extend(item.path for item in sort.sort(album.items()))
            item_type = "album"

        # Perform item query and add tracks to playlist.
        else:
            selection = lib.items(ui.decargs(args))
            paths = [item.path for item in selection]
            if relative_to:
                paths = [relpath(path, relative_to) for path in paths]
            item_type = "track"

        item_type += "s" if len(selection) > 1 else ""

        if not selection:
            ui.print_(ui.colorize("text_warning", "No {0} to play.".format(item_type)))
            return

        # Warn user before playing any huge playlists.
        if len(selection) > 100:
            ui.print_(ui.colorize("text_warning", "You are about to queue {0} {1}.".format(len(selection), item_type)))

            if ui.input_options(("Continue", "Abort")) == "a":
                return

        ui.print_("Playing {0} {1}.".format(len(selection), item_type))
        if raw:
            open_args = paths
        else:
            open_args = self._create_tmp_playlist(paths)

        self._log.debug("executing command: {} {}", command_str, b'"' + b" ".join(open_args) + b'"')
        try:
            util.interactive_open(open_args, command_str)
        except OSError as exc:
            raise ui.UserError("Could not play the music playlist: " "{0}".format(exc))
        finally:
            if not raw:
                self._log.debug("Removing temporary playlist: {}", open_args[0])
                util.remove(open_args[0])
Пример #53
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)
Пример #54
0
def play_music(lib, opts, args):
    """Execute query, create temporary playlist and execute player
    command passing that playlist.
    """
    command_str = config['play']['command'].get()
    use_folders = config['play']['use_folders'].get(bool)
    relative_to = config['play']['relative_to'].get()
    if relative_to:
        relative_to = util.normpath(relative_to)
    if command_str:
        command = shlex.split(command_str)
    else:
        # If a command isn't set, then let the OS decide how to open the
        # playlist.
        sys_name = platform.system()
        if sys_name == 'Darwin':
            command = ['open']
        elif sys_name == 'Windows':
            command = ['start']
        else:
            # If not Mac or Windows, then assume Unixy.
            command = ['xdg-open']

    # Preform search by album and add folders rather then tracks to playlist.
    if opts.album:
        selection = lib.albums(ui.decargs(args))
        paths = []

        for album in selection:
            if use_folders:
                paths.append(album.item_dir())
            else:
                # TODO use core's sorting functionality
                paths.extend([item.path for item in sorted(
                    album.items(), key=lambda item: (item.disc, item.track))])
        item_type = 'album'

    # Preform item query and add tracks to playlist.
    else:
        selection = lib.items(ui.decargs(args))
        paths = [item.path for item in selection]
        item_type = 'track'

    item_type += 's' if len(selection) > 1 else ''

    if not selection:
        ui.print_(ui.colorize('yellow', 'No {0} to play.'.format(item_type)))
        return

    # Warn user before playing any huge playlists.
    if len(selection) > 100:
        ui.print_(ui.colorize(
            'yellow',
            'You are about to queue {0} {1}.'.format(len(selection), item_type)
        ))

        if ui.input_options(('Continue', 'Abort')) == 'a':
            return

    # Create temporary m3u file to hold our playlist.
    m3u = NamedTemporaryFile('w', suffix='.m3u', delete=False)
    for item in paths:
        if relative_to:
            m3u.write(relpath(item, relative_to) + '\n')
        else:
            m3u.write(item + '\n')
    m3u.close()

    command.append(m3u.name)

    # Invoke the command and log the output.
    output = util.command_output(command)
    if output:
        log.debug(u'Output of {0}: {1}'.format(
            util.displayable_path(command[0]),
            output.decode('utf8', 'ignore'),
        ))

    ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type))

    util.remove(m3u.name)
Пример #55
0
def _cleanup(task, session):
    for path in task.old_paths:
        if path in _temp_files:
            if os.path.isfile(path):
                util.remove(path)
            _temp_files.remove(path)
Пример #56
0
 def test_soft_remove_deletes_file(self):
     util.remove(self.path, True)
     self.assertNotExists(self.path)
Пример #57
0
 def test_soft_remove_silent_on_no_file(self):
     try:
         util.remove(self.path + 'XXX', True)
     except OSError:
         self.fail('OSError when removing path')