diff --git a/clean-tags.py b/clean-tags.py new file mode 100755 index 0000000..a8ecd65 --- /dev/null +++ b/clean-tags.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + + +# Dependencies: +# – python-mutagen + + +import argparse +import logging +import os + +from mutagen.id3 import ID3 +from mutagen.flac import FLAC +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', '.flac', '.ogg'] + TAGS_ID3 = ['TALB', 'TDRC', 'TIT2', 'TPE1', 'TRCK'] + TAGS_VORBIS_COMMENTS = ['ALBUM', 'ARTIST', 'DATE', 'TITLE', 'TRACKNUMBER'] + + + def __init__(self, folder): + """Construct a new tag cleaner instancce.""" + self._logger = logging.getLogger(__class__.__name__) + self._dry = False + self._id3 = True + self._flac = True + 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 set_id3(self, do): + """Set whether to clean ID3 tags or not.""" + self._id3 = do + + + def set_flac(self, do): + """Set whether to clean FLAC vorbis comments or not.""" + self._flac = do + + + 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", len(self._files)) + + + def _clean_file(self, filename): + """Clean a file.""" + self._logger.info("Clean file \"%s\"", filename) + if os.path.isfile(filename): + # ID3 + if self._id3: + self._clean_id3(filename) + # FLAC + if self._flac: + self._clean_flac(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.info("Cleaning of ID3 tags failed: %s", e) + + + def _clean_flac(self, filename): + """Clean FLAC vorbis comments.""" + self._logger.info("Clean FLAC vorbis comments") + try: + flac = FLAC(filename) + invalid_comments = {} + if flac.tags: + invalid_comments = self._clean_vorbis_comments(flac.tags) + # Delete comments + if invalid_comments: + for key in invalid_comments.keys(): + print("Delete", key) + del flac.tags[key] + if not self._dry: + flac.save() + print("File saved") + else: + print("File not saved (running in dry mode)") + else: + self._logger.info("Clean, nothing to do") + except MutagenError as e: + self._logger.info("Cleaning of FLAC vorbis comments failed: %s", e) + + + def _clean_vorbis_comments(self, comments): + invalid_comments = {} + for key, value in comments: + if key not in TagCleaner.TAGS_VORBIS_COMMENTS: + invalid_comments[key] = value + if invalid_comments: + print("Unwanted comments:") + for key in invalid_comments.keys(): + print("{}{}: {}".format(TagCleaner.INDENTATION, key, invalid_comments[key])) + + return invalid_comments + + + + +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('--no-id3', dest='id3', action='store_false', help="disable cleaning of ID3 tags") + parser.add_argument('--no-fac', dest='flac', action='store_false', help="disable cleaning of FLAC vorbis comments") + parser.add_argument('folder', help="source folder to read audio files from") + parser.set_defaults(id3=True, flac=True) + 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) + tag_cleaner.set_id3(args.id3) + tag_cleaner.set_flac(args.flac) + + # Run action + tag_cleaner.clean_files() diff --git a/mail-count.bash b/mail-count.bash new file mode 100755 index 0000000..a6bd394 --- /dev/null +++ b/mail-count.bash @@ -0,0 +1,17 @@ +#!/bin/bash + +# Count the number of unread emails and return the count in the i3/sway bar +# format. + +# Settings +MAILDIRNEW="$HOME/Dokumente/eMails/*/INBOX/new/" + +# Count new mails +MAILNEW="$(find $MAILDIRNEW -type f | wc -l)" + +# Print count in i3/sway bar format +echo $MAILNEW +echo $MAILNEW +[ ${MAILNEW} -ge 1 ] && exit 33 + +exit 0 diff --git a/pa.bash b/pa.bash new file mode 100755 index 0000000..7ecd045 --- /dev/null +++ b/pa.bash @@ -0,0 +1,28 @@ +#!/bin/bash + + +# Detect current profile +function profile { + CURRENT_PROFILE=$(LC_ALL=C pactl list sinks | grep -i "active port" | cut -d " " -f 3) +} + + +# If either i3’s button is 1 or the first argument is “switch”, switch profile +if [[ $BLOCK_BUTTON -eq 1 ]] || [ "$1" == "switch" ] ; then + profile + if ! [[ "$CURRENT_PROFILE" == *"iec958"* ]] ; then + pacmd set-card-profile 0 output:iec958-stereo + else + pacmd set-card-profile 0 output:hdmi-stereo + fi +fi + +# Print out current profile +profile +if [[ "$CURRENT_PROFILE" == *"iec958"* ]] ; then + echo "digital" +elif [[ "$CURRENT_PROFILE" == *"hdmi"* ]] ; then + echo "hdmi" +else + echo "${CURRENT_PROFILE%%-*}" +fi diff --git a/pa.sh b/pa.sh new file mode 100755 index 0000000..8c8d9bc --- /dev/null +++ b/pa.sh @@ -0,0 +1,28 @@ +#!/bin/sh + + +# Detect current profile +profile() { + CURRENT_PROFILE=$(LC_ALL=C pactl list sinks | grep -i "active port" | cut -d " " -f 3) +} + + +# If either i3’s button is 1 or the first argument is “switch”, switch profile +if [[ $BLOCK_BUTTON -eq 1 ]] || [ "$1" == "switch" ] ; then + profile + if ! [[ "$CURRENT_PROFILE" == *"iec958"* ]] ; then + pacmd set-card-profile 0 output:iec958-stereo + else + pacmd set-card-profile 0 output:hdmi-stereo + fi +fi + +# Print out current profile +profile +if [[ "$CURRENT_PROFILE" == *"iec958"* ]] ; then + echo "digital" +elif [[ "$CURRENT_PROFILE" == *"hdmi"* ]] ; then + echo "hdmi" +else + echo "${CURRENT_PROFILE%%-*}" +fi diff --git a/passmenu b/passmenu new file mode 100755 index 0000000..5108e6e --- /dev/null +++ b/passmenu @@ -0,0 +1,17 @@ +#!/bin/sh +set -e +shopt -s nullglob globstar + +# Present menu to select password +prefix=${PASSWORD_STORE_DIR-~/.password-store} +password_files=( "$prefix"/**/*.gpg ) +password_files=( "${password_files[@]#"$prefix"/}" ) +password_files=( "${password_files[@]%.gpg}" ) +password=$(printf '%s\n' "${password_files[@]}" | bemenu $@) + +# Copy password to clipboard +pass show -c "$password" 2>/dev/null + +# Send notification +notify-send -u critical -t 45000 "Password in clipboard" \ + "The password for \"$password\" is currently in the clipboard." diff --git a/passusermenu b/passusermenu new file mode 100755 index 0000000..36f71f2 --- /dev/null +++ b/passusermenu @@ -0,0 +1,17 @@ +#!/bin/sh +set -e +shopt -s nullglob globstar + +# Present menu to select password +prefix=${PASSWORD_STORE_DIR-~/.password-store} +password_files=( "$prefix"/**/*.gpg ) +password_files=( "${password_files[@]#"$prefix"/}" ) +password_files=( "${password_files[@]%.gpg}" ) +password=$(printf '%s\n' "${password_files[@]}" | bemenu $@) + +# Copy password to clipboard +pass user "$password" 2>/dev/null + +# Send notification +notify-send -u critical -t 45000 "Username in clipboard" \ + "The username for \"$password\" is currently in the clipboard." diff --git a/timewmenu b/timewmenu deleted file mode 100755 index ebe9e9a..0000000 --- a/timewmenu +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -set -e -shopt -s nullglob globstar - -# Present menu to select tag -tag=$(timew tags | tail +4 | cut -d " " -f 1 | bemenu $@) -tag=$(echo "${tag%-*}" | xargs echo -n) - -# Start timetracking -output=$(timew start "$tag") - -# Send notification -notify-send -t 5000 "timew" "${output}"