We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
scrub: The `auto` option should be a no-op when `import.write` is disabled
### Problem

I was importing new tracks with `import.write` disabled and seeing all tracks populated with new tags. With help from the [forum](https://discourse.beets.io/t/tags-are-being-written-to-imported-files-even-though-import-write-no/2068/4), I was able to determine that with `scrub.auto` enabled and `import.write` disabled, the scrub plugin removes tags and rewrites them from the database.
The [scrub documentation](https://beets.readthedocs.io/en/stable/plugins/scrub.html#automatic-scrubbing) indicates that scrubbing on import is only active if `import.write` is also enabled.
> When importing new files (with import.write turned on) or modifying files’ tags with the beet modify command, beets will first strip all types of tags entirely and then write the database-tracked metadata to the file.

I'd think a resolution could be:
- Update the documentation to indicate scrub doesn't read the `import.write` setting.
- Or scrub _should_ read that setting.
- Or, even better, have a separate scrub configuration that disables restoring tags after the scrub.

### Setup

* OS: Docker https://hub.docker.com/r/linuxserver/beets
* Python version: 3.9.7
* beets version: 1.6.0
* Turning off plugins made problem go away (yes/no): yes

My configuration (output of `beet config`) is:

```yaml
lyrics:
    bing_lang_from: []
    auto: no
    fallback: ''
    sources: genius
    bing_client_secret: REDACTED
    bing_lang_to:
    google_API_key: REDACTED
    google_engine_ID: REDACTED
    genius_api_key: REDACTED
    force: no
    local: no

plugins:
- fetchart
- embedart
- convert
- scrub
- replaygain
- lyrics
- lastgenre
- the
- inline
- chroma
- web
- permissions
directory: /music
library: /config/musiclibrary.blb
art_filename: _cover
threaded: yes
original_date: yes
per_disc_numbering: yes
chroma:
    auto: yes
embedart:
    auto: no
    maxwidth: 0
    compare_threshold: 0
    ifempty: no
    remove_art_file: no
    quality: 0
convert:
    auto: no
    dest: /converted
    format: aac
    formats:
        aac:
            command: ffmpeg -i $source -c:a aac -vbr 4 $dest
            extension: m4a
        mp3:
            command: /config/gapless-mp3.sh $source $dest
            extension: mp3
        alac:
            command: ffmpeg -i $source -y -vn -acodec alac $dest
            extension: m4a
        flac: ffmpeg -i $source -y -vn -acodec flac $dest
        opus: ffmpeg -i $source -y -vn -acodec libopus -ab 96k $dest
        ogg: ffmpeg -i $source -y -vn -acodec libvorbis -aq 3 $dest
        wma: ffmpeg -i $source -y -vn -acodec wmav2 -vn $dest
    pretend: no
    link: no
    hardlink: no
    threads: 8
    id3v23: inherit
    max_bitrate: 500
    tmpdir:
    quiet: no
    embed: yes

    paths: {}
    no_convert: ''
    never_convert_lossy_files: no
    copy_album_art: no
    album_art_maxwidth: 0
    delete_originals: no
item_fields:
    disc_and_track: u'%02i.%02i' % (disc, track) if disctotal > 1 else u'%02i' % (track)
the:
    a: no
    the: yes
    format: '{0}, {1}'
    strip: no
    patterns: []

paths:
    default: '%the{$albumartist}/$original_year - $album%aunique{}/$disc_and_track $title'
    singleton: '%the{$albumartist}/_singles/$original_year - $title'
    comp: _Compilations/$original_year - $album%aunique{}/$disc_and_track $title
    albumtype_soundtrack: _Soundtracks/$original_year - $album%aunique{}/$disc_and_track $title
permissions:
    file: 664
    dir: 775

import:
    write: no
    copy: no
    move: yes
    resume: ask
    incremental: no
    quiet_fallback: skip
    timid: no
    log: /config/beet.log
lastgenre:
    auto: yes
    source: album
    whitelist: yes
    min_weight: 10
    count: 1
    fallback:
    canonical: no
    force: yes
    separator: ', '
    prefer_specific: no
    title_case: yes
fetchart:
    auto: yes
    store_source: yes
    minwidth: 600
    sources:
    - filesystem
    - coverart
    - itunes
    - albumart
    - amazon
    maxwidth: 0
    quality: 0
    max_filesize: 0
    enforce_ratio: no
    cautious: no
    cover_names:
    - cover
    - front
    - art
    - album
    - folder
    google_key: REDACTED
    google_engine: 001442825323518660753:hrh5ch1gjzm
    fanarttv_key: REDACTED
    lastfm_key: REDACTED
    high_resolution: no
    deinterlace: no
    cover_format:
replaygain:
    auto: yes
    backend: ffmpeg
    overwrite: no
    threads: 8
    parallel_on_import: no
    per_disc: no
    peak: 'true'
    targetlevel: 89
    r128: [Opus]
    r128_targetlevel: 84
scrub:
    auto: yes

replace:
    ^\.: _
    '[\x00-\x1f]': _
    '[<>:"\?\*\|]': _
    '[\xE8-\xEB]': e
    '[\xEC-\xEF]': i
    '[\xE2-\xE6]': a
    '[\xF2-\xF6]': o
    '[\xF8]': o
    \.$: _
    \s+$: ''
web:
    host: 0.0.0.0
    port: 8337
    cors: ''
    cors_supports_credentials: no
    reverse_proxy: no
    include_paths: no
    readonly: yes
pathfields: {}
album_fields: {}

```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `beetsplug/scrub.py`
Content:
```
1 # This file is part of beets.
2 # Copyright 2016, Adrian Sampson.
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining
5 # a copy of this software and associated documentation files (the
6 # "Software"), to deal in the Software without restriction, including
7 # without limitation the rights to use, copy, modify, merge, publish,
8 # distribute, sublicense, and/or sell copies of the Software, and to
9 # permit persons to whom the Software is furnished to do so, subject to
10 # the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be
13 # included in all copies or substantial portions of the Software.
14 
15 """Cleans extraneous metadata from files' tags via a command or
16 automatically whenever tags are written.
17 """
18 
19 
20 from beets.plugins import BeetsPlugin
21 from beets import ui
22 from beets import util
23 from beets import config
24 import mediafile
25 import mutagen
26 
27 _MUTAGEN_FORMATS = {
28     'asf': 'ASF',
29     'apev2': 'APEv2File',
30     'flac': 'FLAC',
31     'id3': 'ID3FileType',
32     'mp3': 'MP3',
33     'mp4': 'MP4',
34     'oggflac': 'OggFLAC',
35     'oggspeex': 'OggSpeex',
36     'oggtheora': 'OggTheora',
37     'oggvorbis': 'OggVorbis',
38     'oggopus': 'OggOpus',
39     'trueaudio': 'TrueAudio',
40     'wavpack': 'WavPack',
41     'monkeysaudio': 'MonkeysAudio',
42     'optimfrog': 'OptimFROG',
43 }
44 
45 
46 class ScrubPlugin(BeetsPlugin):
47     """Removes extraneous metadata from files' tags."""
48     def __init__(self):
49         super().__init__()
50         self.config.add({
51             'auto': True,
52         })
53 
54         if self.config['auto']:
55             self.register_listener("import_task_files", self.import_task_files)
56 
57     def commands(self):
58         def scrub_func(lib, opts, args):
59             # Walk through matching files and remove tags.
60             for item in lib.items(ui.decargs(args)):
61                 self._log.info('scrubbing: {0}',
62                                util.displayable_path(item.path))
63                 self._scrub_item(item, opts.write)
64 
65         scrub_cmd = ui.Subcommand('scrub', help='clean audio tags')
66         scrub_cmd.parser.add_option(
67             '-W', '--nowrite', dest='write',
68             action='store_false', default=True,
69             help='leave tags empty')
70         scrub_cmd.func = scrub_func
71 
72         return [scrub_cmd]
73 
74     @staticmethod
75     def _mutagen_classes():
76         """Get a list of file type classes from the Mutagen module.
77         """
78         classes = []
79         for modname, clsname in _MUTAGEN_FORMATS.items():
80             mod = __import__(f'mutagen.{modname}',
81                              fromlist=[clsname])
82             classes.append(getattr(mod, clsname))
83         return classes
84 
85     def _scrub(self, path):
86         """Remove all tags from a file.
87         """
88         for cls in self._mutagen_classes():
89             # Try opening the file with this type, but just skip in the
90             # event of any error.
91             try:
92                 f = cls(util.syspath(path))
93             except Exception:
94                 continue
95             if f.tags is None:
96                 continue
97 
98             # Remove the tag for this type.
99             try:
100                 f.delete()
101             except NotImplementedError:
102                 # Some Mutagen metadata subclasses (namely, ASFTag) do not
103                 # support .delete(), presumably because it is impossible to
104                 # remove them. In this case, we just remove all the tags.
105                 for tag in f.keys():
106                     del f[tag]
107                 f.save()
108             except (OSError, mutagen.MutagenError) as exc:
109                 self._log.error('could not scrub {0}: {1}',
110                                 util.displayable_path(path), exc)
111 
112     def _scrub_item(self, item, restore=True):
113         """Remove tags from an Item's associated file and, if `restore`
114         is enabled, write the database's tags back to the file.
115         """
116         # Get album art if we need to restore it.
117         if restore:
118             try:
119                 mf = mediafile.MediaFile(util.syspath(item.path),
120                                          config['id3v23'].get(bool))
121             except mediafile.UnreadableFileError as exc:
122                 self._log.error('could not open file to scrub: {0}',
123                                 exc)
124                 return
125             images = mf.images
126 
127         # Remove all tags.
128         self._scrub(item.path)
129 
130         # Restore tags, if enabled.
131         if restore:
132             self._log.debug('writing new tags after scrub')
133             item.try_write()
134             if images:
135                 self._log.debug('restoring art')
136                 try:
137                     mf = mediafile.MediaFile(util.syspath(item.path),
138                                              config['id3v23'].get(bool))
139                     mf.images = images
140                     mf.save()
141                 except mediafile.UnreadableFileError as exc:
142                     self._log.error('could not write tags: {0}', exc)
143 
144     def import_task_files(self, session, task):
145         """Automatically scrub imported files."""
146         for item in task.imported_items():
147             self._log.debug('auto-scrubbing {0}',
148                             util.displayable_path(item.path))
149             self._scrub_item(item)
150
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/beetsplug/scrub.py b/beetsplug/scrub.py
--- a/beetsplug/scrub.py
+++ b/beetsplug/scrub.py
@@ -109,7 +109,7 @@
                 self._log.error('could not scrub {0}: {1}',
                                 util.displayable_path(path), exc)
 
-    def _scrub_item(self, item, restore=True):
+    def _scrub_item(self, item, restore):
         """Remove tags from an Item's associated file and, if `restore`
         is enabled, write the database's tags back to the file.
         """
@@ -146,4 +146,4 @@
         for item in task.imported_items():
             self._log.debug('auto-scrubbing {0}',
                             util.displayable_path(item.path))
-            self._scrub_item(item)
+            self._scrub_item(item, ui.should_write())

{"golden_diff": "diff --git a/beetsplug/scrub.py b/beetsplug/scrub.py\n--- a/beetsplug/scrub.py\n+++ b/beetsplug/scrub.py\n@@ -109,7 +109,7 @@\n                 self._log.error('could not scrub {0}: {1}',\n                                 util.displayable_path(path), exc)\n \n-    def _scrub_item(self, item, restore=True):\n+    def _scrub_item(self, item, restore):\n         \"\"\"Remove tags from an Item's associated file and, if `restore`\n         is enabled, write the database's tags back to the file.\n         \"\"\"\n@@ -146,4 +146,4 @@\n         for item in task.imported_items():\n             self._log.debug('auto-scrubbing {0}',\n                             util.displayable_path(item.path))\n-            self._scrub_item(item)\n+            self._scrub_item(item, ui.should_write())\n", "issue": "scrub: The `auto` option should be a no-op when `import.write` is disabled\n### Problem\r\n\r\nI was importing new tracks with `import.write` disabled and seeing all tracks populated with new tags. With help from the [forum](https://discourse.beets.io/t/tags-are-being-written-to-imported-files-even-though-import-write-no/2068/4), I was able to determine that with `scrub.auto` enabled and `import.write` disabled, the scrub plugin removes tags and rewrites them from the database.\r\nThe [scrub documentation](https://beets.readthedocs.io/en/stable/plugins/scrub.html#automatic-scrubbing) indicates that scrubbing on import is only active if `import.write` is also enabled.\r\n> When importing new files (with import.write turned on) or modifying files\u2019 tags with the beet modify command, beets will first strip all types of tags entirely and then write the database-tracked metadata to the file.\r\n\r\nI'd think a resolution could be:\r\n- Update the documentation to indicate scrub doesn't read the `import.write` setting.\r\n- Or scrub _should_ read that setting.\r\n- Or, even better, have a separate scrub configuration that disables restoring tags after the scrub.\r\n\r\n### Setup\r\n\r\n* OS: Docker https://hub.docker.com/r/linuxserver/beets\r\n* Python version: 3.9.7\r\n* beets version: 1.6.0\r\n* Turning off plugins made problem go away (yes/no): yes\r\n\r\nMy configuration (output of `beet config`) is:\r\n\r\n```yaml\r\nlyrics:\r\n    bing_lang_from: []\r\n    auto: no\r\n    fallback: ''\r\n    sources: genius\r\n    bing_client_secret: REDACTED\r\n    bing_lang_to:\r\n    google_API_key: REDACTED\r\n    google_engine_ID: REDACTED\r\n    genius_api_key: REDACTED\r\n    force: no\r\n    local: no\r\n\r\nplugins:\r\n- fetchart\r\n- embedart\r\n- convert\r\n- scrub\r\n- replaygain\r\n- lyrics\r\n- lastgenre\r\n- the\r\n- inline\r\n- chroma\r\n- web\r\n- permissions\r\ndirectory: /music\r\nlibrary: /config/musiclibrary.blb\r\nart_filename: _cover\r\nthreaded: yes\r\noriginal_date: yes\r\nper_disc_numbering: yes\r\nchroma:\r\n    auto: yes\r\nembedart:\r\n    auto: no\r\n    maxwidth: 0\r\n    compare_threshold: 0\r\n    ifempty: no\r\n    remove_art_file: no\r\n    quality: 0\r\nconvert:\r\n    auto: no\r\n    dest: /converted\r\n    format: aac\r\n    formats:\r\n        aac:\r\n            command: ffmpeg -i $source -c:a aac -vbr 4 $dest\r\n            extension: m4a\r\n        mp3:\r\n            command: /config/gapless-mp3.sh $source $dest\r\n            extension: mp3\r\n        alac:\r\n            command: ffmpeg -i $source -y -vn -acodec alac $dest\r\n            extension: m4a\r\n        flac: ffmpeg -i $source -y -vn -acodec flac $dest\r\n        opus: ffmpeg -i $source -y -vn -acodec libopus -ab 96k $dest\r\n        ogg: ffmpeg -i $source -y -vn -acodec libvorbis -aq 3 $dest\r\n        wma: ffmpeg -i $source -y -vn -acodec wmav2 -vn $dest\r\n    pretend: no\r\n    link: no\r\n    hardlink: no\r\n    threads: 8\r\n    id3v23: inherit\r\n    max_bitrate: 500\r\n    tmpdir:\r\n    quiet: no\r\n    embed: yes\r\n\r\n    paths: {}\r\n    no_convert: ''\r\n    never_convert_lossy_files: no\r\n    copy_album_art: no\r\n    album_art_maxwidth: 0\r\n    delete_originals: no\r\nitem_fields:\r\n    disc_and_track: u'%02i.%02i' % (disc, track) if disctotal > 1 else u'%02i' % (track)\r\nthe:\r\n    a: no\r\n    the: yes\r\n    format: '{0}, {1}'\r\n    strip: no\r\n    patterns: []\r\n\r\npaths:\r\n    default: '%the{$albumartist}/$original_year - $album%aunique{}/$disc_and_track $title'\r\n    singleton: '%the{$albumartist}/_singles/$original_year - $title'\r\n    comp: _Compilations/$original_year - $album%aunique{}/$disc_and_track $title\r\n    albumtype_soundtrack: _Soundtracks/$original_year - $album%aunique{}/$disc_and_track $title\r\npermissions:\r\n    file: 664\r\n    dir: 775\r\n\r\nimport:\r\n    write: no\r\n    copy: no\r\n    move: yes\r\n    resume: ask\r\n    incremental: no\r\n    quiet_fallback: skip\r\n    timid: no\r\n    log: /config/beet.log\r\nlastgenre:\r\n    auto: yes\r\n    source: album\r\n    whitelist: yes\r\n    min_weight: 10\r\n    count: 1\r\n    fallback:\r\n    canonical: no\r\n    force: yes\r\n    separator: ', '\r\n    prefer_specific: no\r\n    title_case: yes\r\nfetchart:\r\n    auto: yes\r\n    store_source: yes\r\n    minwidth: 600\r\n    sources:\r\n    - filesystem\r\n    - coverart\r\n    - itunes\r\n    - albumart\r\n    - amazon\r\n    maxwidth: 0\r\n    quality: 0\r\n    max_filesize: 0\r\n    enforce_ratio: no\r\n    cautious: no\r\n    cover_names:\r\n    - cover\r\n    - front\r\n    - art\r\n    - album\r\n    - folder\r\n    google_key: REDACTED\r\n    google_engine: 001442825323518660753:hrh5ch1gjzm\r\n    fanarttv_key: REDACTED\r\n    lastfm_key: REDACTED\r\n    high_resolution: no\r\n    deinterlace: no\r\n    cover_format:\r\nreplaygain:\r\n    auto: yes\r\n    backend: ffmpeg\r\n    overwrite: no\r\n    threads: 8\r\n    parallel_on_import: no\r\n    per_disc: no\r\n    peak: 'true'\r\n    targetlevel: 89\r\n    r128: [Opus]\r\n    r128_targetlevel: 84\r\nscrub:\r\n    auto: yes\r\n\r\nreplace:\r\n    ^\\.: _\r\n    '[\\x00-\\x1f]': _\r\n    '[<>:\"\\?\\*\\|]': _\r\n    '[\\xE8-\\xEB]': e\r\n    '[\\xEC-\\xEF]': i\r\n    '[\\xE2-\\xE6]': a\r\n    '[\\xF2-\\xF6]': o\r\n    '[\\xF8]': o\r\n    \\.$: _\r\n    \\s+$: ''\r\nweb:\r\n    host: 0.0.0.0\r\n    port: 8337\r\n    cors: ''\r\n    cors_supports_credentials: no\r\n    reverse_proxy: no\r\n    include_paths: no\r\n    readonly: yes\r\npathfields: {}\r\nalbum_fields: {}\r\n\r\n```\r\n\n", "before_files": [{"content": "# This file is part of beets.\n# Copyright 2016, Adrian Sampson.\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the Software, and to\n# permit persons to whom the Software is furnished to do so, subject to\n# the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n\n\"\"\"Cleans extraneous metadata from files' tags via a command or\nautomatically whenever tags are written.\n\"\"\"\n\n\nfrom beets.plugins import BeetsPlugin\nfrom beets import ui\nfrom beets import util\nfrom beets import config\nimport mediafile\nimport mutagen\n\n_MUTAGEN_FORMATS = {\n    'asf': 'ASF',\n    'apev2': 'APEv2File',\n    'flac': 'FLAC',\n    'id3': 'ID3FileType',\n    'mp3': 'MP3',\n    'mp4': 'MP4',\n    'oggflac': 'OggFLAC',\n    'oggspeex': 'OggSpeex',\n    'oggtheora': 'OggTheora',\n    'oggvorbis': 'OggVorbis',\n    'oggopus': 'OggOpus',\n    'trueaudio': 'TrueAudio',\n    'wavpack': 'WavPack',\n    'monkeysaudio': 'MonkeysAudio',\n    'optimfrog': 'OptimFROG',\n}\n\n\nclass ScrubPlugin(BeetsPlugin):\n    \"\"\"Removes extraneous metadata from files' tags.\"\"\"\n    def __init__(self):\n        super().__init__()\n        self.config.add({\n            'auto': True,\n        })\n\n        if self.config['auto']:\n            self.register_listener(\"import_task_files\", self.import_task_files)\n\n    def commands(self):\n        def scrub_func(lib, opts, args):\n            # Walk through matching files and remove tags.\n            for item in lib.items(ui.decargs(args)):\n                self._log.info('scrubbing: {0}',\n                               util.displayable_path(item.path))\n                self._scrub_item(item, opts.write)\n\n        scrub_cmd = ui.Subcommand('scrub', help='clean audio tags')\n        scrub_cmd.parser.add_option(\n            '-W', '--nowrite', dest='write',\n            action='store_false', default=True,\n            help='leave tags empty')\n        scrub_cmd.func = scrub_func\n\n        return [scrub_cmd]\n\n    @staticmethod\n    def _mutagen_classes():\n        \"\"\"Get a list of file type classes from the Mutagen module.\n        \"\"\"\n        classes = []\n        for modname, clsname in _MUTAGEN_FORMATS.items():\n            mod = __import__(f'mutagen.{modname}',\n                             fromlist=[clsname])\n            classes.append(getattr(mod, clsname))\n        return classes\n\n    def _scrub(self, path):\n        \"\"\"Remove all tags from a file.\n        \"\"\"\n        for cls in self._mutagen_classes():\n            # Try opening the file with this type, but just skip in the\n            # event of any error.\n            try:\n                f = cls(util.syspath(path))\n            except Exception:\n                continue\n            if f.tags is None:\n                continue\n\n            # Remove the tag for this type.\n            try:\n                f.delete()\n            except NotImplementedError:\n                # Some Mutagen metadata subclasses (namely, ASFTag) do not\n                # support .delete(), presumably because it is impossible to\n                # remove them. In this case, we just remove all the tags.\n                for tag in f.keys():\n                    del f[tag]\n                f.save()\n            except (OSError, mutagen.MutagenError) as exc:\n                self._log.error('could not scrub {0}: {1}',\n                                util.displayable_path(path), exc)\n\n    def _scrub_item(self, item, restore=True):\n        \"\"\"Remove tags from an Item's associated file and, if `restore`\n        is enabled, write the database's tags back to the file.\n        \"\"\"\n        # Get album art if we need to restore it.\n        if restore:\n            try:\n                mf = mediafile.MediaFile(util.syspath(item.path),\n                                         config['id3v23'].get(bool))\n            except mediafile.UnreadableFileError as exc:\n                self._log.error('could not open file to scrub: {0}',\n                                exc)\n                return\n            images = mf.images\n\n        # Remove all tags.\n        self._scrub(item.path)\n\n        # Restore tags, if enabled.\n        if restore:\n            self._log.debug('writing new tags after scrub')\n            item.try_write()\n            if images:\n                self._log.debug('restoring art')\n                try:\n                    mf = mediafile.MediaFile(util.syspath(item.path),\n                                             config['id3v23'].get(bool))\n                    mf.images = images\n                    mf.save()\n                except mediafile.UnreadableFileError as exc:\n                    self._log.error('could not write tags: {0}', exc)\n\n    def import_task_files(self, session, task):\n        \"\"\"Automatically scrub imported files.\"\"\"\n        for item in task.imported_items():\n            self._log.debug('auto-scrubbing {0}',\n                            util.displayable_path(item.path))\n            self._scrub_item(item)\n", "path": "beetsplug/scrub.py"}], "after_files": [{"content": "# This file is part of beets.\n# Copyright 2016, Adrian Sampson.\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the Software, and to\n# permit persons to whom the Software is furnished to do so, subject to\n# the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n\n\"\"\"Cleans extraneous metadata from files' tags via a command or\nautomatically whenever tags are written.\n\"\"\"\n\n\nfrom beets.plugins import BeetsPlugin\nfrom beets import ui\nfrom beets import util\nfrom beets import config\nimport mediafile\nimport mutagen\n\n_MUTAGEN_FORMATS = {\n    'asf': 'ASF',\n    'apev2': 'APEv2File',\n    'flac': 'FLAC',\n    'id3': 'ID3FileType',\n    'mp3': 'MP3',\n    'mp4': 'MP4',\n    'oggflac': 'OggFLAC',\n    'oggspeex': 'OggSpeex',\n    'oggtheora': 'OggTheora',\n    'oggvorbis': 'OggVorbis',\n    'oggopus': 'OggOpus',\n    'trueaudio': 'TrueAudio',\n    'wavpack': 'WavPack',\n    'monkeysaudio': 'MonkeysAudio',\n    'optimfrog': 'OptimFROG',\n}\n\n\nclass ScrubPlugin(BeetsPlugin):\n    \"\"\"Removes extraneous metadata from files' tags.\"\"\"\n    def __init__(self):\n        super().__init__()\n        self.config.add({\n            'auto': True,\n        })\n\n        if self.config['auto']:\n            self.register_listener(\"import_task_files\", self.import_task_files)\n\n    def commands(self):\n        def scrub_func(lib, opts, args):\n            # Walk through matching files and remove tags.\n            for item in lib.items(ui.decargs(args)):\n                self._log.info('scrubbing: {0}',\n                               util.displayable_path(item.path))\n                self._scrub_item(item, opts.write)\n\n        scrub_cmd = ui.Subcommand('scrub', help='clean audio tags')\n        scrub_cmd.parser.add_option(\n            '-W', '--nowrite', dest='write',\n            action='store_false', default=True,\n            help='leave tags empty')\n        scrub_cmd.func = scrub_func\n\n        return [scrub_cmd]\n\n    @staticmethod\n    def _mutagen_classes():\n        \"\"\"Get a list of file type classes from the Mutagen module.\n        \"\"\"\n        classes = []\n        for modname, clsname in _MUTAGEN_FORMATS.items():\n            mod = __import__(f'mutagen.{modname}',\n                             fromlist=[clsname])\n            classes.append(getattr(mod, clsname))\n        return classes\n\n    def _scrub(self, path):\n        \"\"\"Remove all tags from a file.\n        \"\"\"\n        for cls in self._mutagen_classes():\n            # Try opening the file with this type, but just skip in the\n            # event of any error.\n            try:\n                f = cls(util.syspath(path))\n            except Exception:\n                continue\n            if f.tags is None:\n                continue\n\n            # Remove the tag for this type.\n            try:\n                f.delete()\n            except NotImplementedError:\n                # Some Mutagen metadata subclasses (namely, ASFTag) do not\n                # support .delete(), presumably because it is impossible to\n                # remove them. In this case, we just remove all the tags.\n                for tag in f.keys():\n                    del f[tag]\n                f.save()\n            except (OSError, mutagen.MutagenError) as exc:\n                self._log.error('could not scrub {0}: {1}',\n                                util.displayable_path(path), exc)\n\n    def _scrub_item(self, item, restore):\n        \"\"\"Remove tags from an Item's associated file and, if `restore`\n        is enabled, write the database's tags back to the file.\n        \"\"\"\n        # Get album art if we need to restore it.\n        if restore:\n            try:\n                mf = mediafile.MediaFile(util.syspath(item.path),\n                                         config['id3v23'].get(bool))\n            except mediafile.UnreadableFileError as exc:\n                self._log.error('could not open file to scrub: {0}',\n                                exc)\n                return\n            images = mf.images\n\n        # Remove all tags.\n        self._scrub(item.path)\n\n        # Restore tags, if enabled.\n        if restore:\n            self._log.debug('writing new tags after scrub')\n            item.try_write()\n            if images:\n                self._log.debug('restoring art')\n                try:\n                    mf = mediafile.MediaFile(util.syspath(item.path),\n                                             config['id3v23'].get(bool))\n                    mf.images = images\n                    mf.save()\n                except mediafile.UnreadableFileError as exc:\n                    self._log.error('could not write tags: {0}', exc)\n\n    def import_task_files(self, session, task):\n        \"\"\"Automatically scrub imported files.\"\"\"\n        for item in task.imported_items():\n            self._log.debug('auto-scrubbing {0}',\n                            util.displayable_path(item.path))\n            self._scrub_item(item, ui.should_write())\n", "path": "beetsplug/scrub.py"}]}