scripts/clean-tags.py
2016-07-26 22:07:12 +02:00

165 lines
5.1 KiB
Python
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Dependencies:
# python-mutagen
import argparse
import logging
import os
from mutagen.id3 import ID3
from mutagen import MutagenError
class Styling:
"""Output styling constants."""
ENDC = '\x1b[0m'
BOLD = '\x1b[1m'
class TagCleaner:
"""
Class to check audio files for unwanted tags and provide routines to clean
them.
"""
INDENTATION = " "
FILE_EXTENSIONS = ['.mp3']
TAGS_ID3 = ['TALB', 'TDRC', 'TIT2', 'TPE1', 'TRCK']
def __init__(self, folder):
"""Construct a new tag cleaner instancce."""
self._logger = logging.getLogger(__class__.__name__)
self._dry = False
self._folder = folder
self._files = []
self._read_files(folder)
def set_dry(self, dry):
"""Set dry mode (do not save any file)."""
self._dry = dry
def clean_files(self):
"""Clean all found files."""
self._logger.info("Clean files")
for filename in self._files:
self._clean_file(filename)
def _read_files(self, folder):
self._logger.info("Read files from \"{}\"".format(folder))
for dirname, dirnames, filenames in os.walk(folder):
for filename in filenames:
if filename.startswith("."):
continue
(name, ext) = os.path.splitext(filename)
if ext.lower() not in TagCleaner.FILE_EXTENSIONS:
continue
self._logger.debug("Found file \"%s\"", os.path.join(dirname, filename))
self._files.append(os.path.join(dirname, filename))
self._logger.info("Found %d files".format(len(self._files)))
def _clean_file(self, filename):
"""Clean a file."""
self._logger.info("Clean file \"%s\"", filename)
if os.path.isfile(filename):
# ID3
self._clean_id3(filename)
else:
self._logger.info("Not a file: \"%s\"", filename)
def _clean_id3(self, filename):
"""Clean ID3 tags."""
self._logger.info("Clean ID3")
try:
tags = ID3(filename)
print(Styling.BOLD + filename[len(self._folder):] + Styling.ENDC)
print("ID3", "v{}.{}.{}".format(*tags.version))
valid = True
# Check version
if tags.version != (2, 4, 0):
valid = False
# Unknown tags
if tags.unknown_frames:
valid = False
print("Unknown frames:")
for frame in tags.unknown_frames:
print(frame)
# Invalid tags
invalid_tags = []
for tag in tags:
if len(tag) > 4:
tag = tag[0:4]
if tag not in TagCleaner.TAGS_ID3:
invalid_tags.append(tag)
if invalid_tags:
valid = False
print("Unwanted tags:")
for tag in invalid_tags:
for frame in tags.getall(tag):
if hasattr(frame, 'text'):
for value in frame.text:
print("{}{}: {}".format(TagCleaner.INDENTATION, tag, value))
else:
print("{}{}:".format(TagCleaner.INDENTATION, tag), frame)
# Save
if not valid:
# Delete tags
for tag in invalid_tags:
print("Delete", tag)
tags.delall(tag)
# Save file
if not self._dry:
try:
tags.save()
print("File saved")
except Exception as e:
self._logger.error("Saving of file \"%s\" failed: %s", filename, e)
else:
print("File not saved (running in dry mode)")
else:
self._logger.info("Clean, nothing to do")
except MutagenError as e:
self._logger.error("Cleaning of ID3 tags failed: %s", e)
if __name__ == "__main__":
# Setup command line
parser = argparse.ArgumentParser("Clean unwanted tags from audio files to keep your music library clean.")
parser.add_argument('-d', '--dry', dest='dry', action='store_true', default=False, help="dry run, do not modify any file, just print information")
parser.add_argument('-v', '--verbose', dest='verbosity', action='count', default=0, help="be verbose, show more information")
parser.add_argument('-l', '--logfile', dest='logfile', help="specify name of logfile")
parser.add_argument('folder', help="source folder to read audio files from")
args = parser.parse_args()
# Setup logging
logging.basicConfig(
filename=args.logfile,
level=logging.ERROR-(10*args.verbosity),
format="%(asctime)s %(levelname)s: %(message)s"
)
# Create tag cleaner instance
tag_cleaner = TagCleaner(args.folder)
tag_cleaner.set_dry(args.dry)
# Run action
tag_cleaner.clean_files()