diff --git a/beetsplug/badfiles.py b/beetsplug/badfiles.py new file mode 100644 index 000000000..6f6a2cfcc --- /dev/null +++ b/beetsplug/badfiles.py @@ -0,0 +1,93 @@ +#!/usr/bin/python +# coding=utf-8 + +# Base Python File (badfiles.py) +# Created: Tue 11 Aug 2015 10:46:34 PM CEST +# Version: 1.0 +# +# This Python script was developped by François-Xavier Thomas. +# You are free to copy, adapt or modify it. +# If you do so, however, leave my name somewhere in the credits, I'd appreciate it ;) +# +# (ɔ) François-Xavier Thomas + +from beets.plugins import BeetsPlugin +from beets.ui import Subcommand +from beets.util import displayable_path +from beets import ui +from subprocess import check_output, CalledProcessError, list2cmdline, STDOUT +import shlex +import os +import errno +import sys + +class BadFiles(BeetsPlugin): + + def run_command(self, cmd): + self._log.debug(u"running command: %s" % displayable_path(list2cmdline(cmd))) + try: + output = check_output(cmd, stderr=STDOUT) + return 0, [line for line in output.split("\n") if line] + except CalledProcessError as e: + return 1, [line for line in e.output.split("\n") if line] + except OSError as e: + if e.errno == errno.ENOENT: + ui.print_("Command '%s' does not exit. Is it installed?" % cmd[0]) + sys.exit(1) + else: + raise + + def check_mp3val(self, path): + errors, output = self.run_command(["mp3val", path]) + if errors == 0: + output = [line for line in output if line.startswith("WARNING:")] + errors = sum(1 for line in output if line.startswith("WARNING:")) + return errors, output + + def check_flac(self, path): + return self.run_command(["flac", "-wst", path]) + + def check_custom(self, command): + def checker(path): + cmd = shlex.split(command) + cmd.append(path) + return self.run_command(cmd) + return checker + + def get_checker(self, ext): + ext = ext.lower() + command = self.config['commands'].get().get(ext) + if command: + return self.check_custom(command) + elif ext == "mp3": + return self.check_mp3val + elif ext == "flac": + return self.check_flac + + def check_bad(self, lib, opts, args): + for item in lib.items(args): + + # First check if the path exists. If not, should run 'beets update' + # to cleanup your library. + dpath = displayable_path(item.path) + self._log.debug(u"checking path: %s" % dpath) + if not os.path.exists(item.path): + ui.print_(u"%s: file does not exist" % dpath) + + # Run the checker against the file if one is found + ext = os.path.splitext(item.path)[1][1:] + checker = self.get_checker(ext) + if not checker: + continue + errors, output = checker(item.path) + if errors == 0: + ui.print_(u"%s: ok" % dpath) + else: + ui.print_(u"%s: checker found %d errors or warnings" % (dpath, errors)) + for line in output: + ui.print_(u" %s" % displayable_path(line)) + + def commands(self): + bad_command = Subcommand('bad', help='check for corrupt or missing files') + bad_command.func = self.check_bad + return [bad_command] diff --git a/docs/changelog.rst b/docs/changelog.rst index 643904268..abbc565c3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -10,6 +10,8 @@ The new features: magenta). Thanks to :user:`mathstuf`. :bug:`1548` * :doc:`/plugins/play`: A new ``--args`` option lets you specify options for the player command. :bug:`1532` +* A new :doc:`/plugins/badfiles` helps you scan for corruption in your music + collection. Fixes: diff --git a/docs/plugins/badfiles.rst b/docs/plugins/badfiles.rst new file mode 100644 index 000000000..d11eefe77 --- /dev/null +++ b/docs/plugins/badfiles.rst @@ -0,0 +1,53 @@ +Bad Files Plugin +================ + +Adds a `beet bad` command to check for missing, and optionally corrupt files. + +Configuration +------------- + +Here is a very basic configuration that uses the default commands for MP3 and +FLAC files, requiring the `mp3val`_ and +packages to be installed:: + + badfiles: + commands: {} + plugins: ... badfiles + +Note that the *mp3val* checker is a bit verbose and can output a lot of "stream +error" messages, even for files that play perfectly well. Generally if more +than one stream error happens, or if a stream error happens in the middle of a +file, this is a bad sign. + +.. _mp3val: http://mp3val.sourceforge.net/ +.. _flac: https://xiph.org/flac/ + +You can also add custom commands for a specific extension, e.g.:: + + badfiles: + commands: + ogg: myoggchecker --opt1 --opt2 + flac: flac --test --warnings-as-errors --silent + plugins: ... badfiles + +Running +------- + +To run Badfiles, just use the ``beet bad`` command with Beets' usual query syntax. + +For instance, this will run a check on all songs containing the word "wolf":: + + beet bad wolf + +This one will run checks on a specific album:: + + beet bad album_id:1234 + +Here is an example from my library where the FLAC decoder was signaling a +corrupt file:: + + beet bad title::^$ + /tank/Music/__/00.flac: command exited with status 1 + 00.flac: *** Got error code 2:FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH + 00.flac: ERROR while decoding data + state = FLAC__STREAM_DECODER_READ_FRAME diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index 654e5dfa2..3f9fbb7af 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -31,6 +31,7 @@ Each plugin has its own set of options that can be defined in a section bearing .. toctree:: :hidden: + badfiles bpd bpm bucket @@ -139,6 +140,7 @@ Interoperability changes. * :doc:`smartplaylist`: Generate smart playlists based on beets queries. * :doc:`thumbnails`: Get thumbnails with the cover art on your album folders. +* :doc:`badfiles`: Check audio file integrity. .. _Plex: http://plex.tv @@ -211,8 +213,6 @@ Here are a few of the plugins written by the beets community: * `beets-noimport`_ adds and removes directories from the incremental import skip list. -* `beets-badfiles`_ helps you identify broken audio files. - .. _beets-check: https://github.com/geigerzaehler/beets-check .. _copyartifacts: https://github.com/sbarakat/beets-copyartifacts .. _dsedivec: https://github.com/dsedivec/beets-plugins @@ -228,4 +228,3 @@ Here are a few of the plugins written by the beets community: .. _beets-follow: https://github.com/nolsto/beets-follow .. _beets-setlister: https://github.com/tomjaspers/beets-setlister .. _beets-noimport: https://github.com/ttsda/beets-noimport -.. _beets-badfiles: https://github.com/fxthomas/beets-badfiles