def main(argv): parser = ID3OptionParser() parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help="print out saved tags", default=False) parser.add_option("--write-v1", action="store_true", dest="write_v1", default=False, help="write id3v1 tags") parser.add_option("-x", "--exclude-tag", metavar="TAG", action="append", dest="x", help="exclude the specified tag", default=[]) parser.add_option("--merge", action="store_true", help="Copy over frames instead of the whole ID3 tag", default=False) (options, args) = parser.parse_args(argv[1:]) if len(args) != 2: parser.print_help(file=sys.stderr) return 1 (src, dst) = args if not os.path.isfile(src): print_(u"File not found:", src, file=sys.stderr) parser.print_help(file=sys.stderr) return 1 if not os.path.isfile(dst): printerr(u"File not found:", dst, file=sys.stderr) parser.print_help(file=sys.stderr) return 1 # Strip tags - "-x FOO" adds whitespace at the beginning of the tag name excluded_tags = [x.strip() for x in options.x] with _sig.block(): return copy(src, dst, options.merge, options.write_v1, excluded_tags, options.verbose)
def delete_frames(deletes, filenames): try: deletes = frame_from_fsnative(deletes) except ValueError as err: print_(text_type(err), file=sys.stderr) frames = deletes.split(",") for filename in filenames: with _sig.block(): if verbose: print_(u"deleting %s from" % deletes, filename, file=sys.stderr) try: id3 = mutagen.id3.ID3(filename) except mutagen.id3.ID3NoHeaderError: if verbose: print_(u"No ID3 header found; skipping.", file=sys.stderr) except Exception as err: print_(text_type(err), file=sys.stderr) raise SystemExit(1) else: for frame in frames: id3.delall(frame) id3.save()
def update(options, filenames): encoding = options.encoding or getpreferredencoding() verbose = options.verbose noupdate = options.noupdate force_v1 = options.force_v1 remove_v1 = options.remove_v1 def conv(uni): return uni.encode('iso-8859-1').decode(encoding) for filename in filenames: with _sig.block(): if verbose != "quiet": print_(u"Updating", filename) if has_id3v1(filename) and not noupdate and force_v1: mutagen.id3.delete(filename, False, True) try: id3 = mutagen.id3.ID3(filename) except mutagen.id3.ID3NoHeaderError: if verbose != "quiet": print_(u"No ID3 header found; skipping...") continue except Exception as err: print_(text_type(err), file=sys.stderr) continue for tag in filter(lambda t: t.startswith(("T", "COMM")), id3): frame = id3[tag] if isinstance(frame, mutagen.id3.TimeStampTextFrame): # non-unicode fields continue try: text = frame.text except AttributeError: continue try: text = [conv(x) for x in frame.text] except (UnicodeError, LookupError): continue else: frame.text = text if not text or min(map(isascii, text)): frame.encoding = 3 else: frame.encoding = 1 if verbose == "debug": print_(id3.pprint()) if not noupdate: if remove_v1: id3.save(filename, v1=False) else: id3.save(filename)
def list_tags(filenames): for filename in filenames: print_("IDv2 tag info for", filename) try: id3 = mutagen.id3.ID3(filename, translate=False) except mutagen.id3.ID3NoHeaderError: print_(u"No ID3 header found; skipping.") except Exception as err: print_(text_type(err), file=sys.stderr) raise SystemExit(1) else: print_(id3.pprint())
def list_tags_raw(filenames): for filename in filenames: print_("Raw IDv2 tag info for", filename) try: id3 = mutagen.id3.ID3(filename, translate=False) except mutagen.id3.ID3NoHeaderError: print_(u"No ID3 header found; skipping.") except Exception as err: print_(str(err), file=sys.stderr) raise SystemExit(1) else: for frame in id3.values(): print_(str(repr(frame)))
def main(argv): from mutagen import File parser = OptionParser() parser.add_option("--no-flac", help="Compatibility; does nothing.") parser.add_option("--no-mp3", help="Compatibility; does nothing.") parser.add_option("--no-apev2", help="Compatibility; does nothing.") (options, args) = parser.parse_args(argv[1:]) if not args: raise SystemExit(parser.print_help() or 1) for filename in args: print_(u"--", filename) try: print_(u"-", File(filename).pprint()) except AttributeError: print_(u"- Unknown file type") except Exception as err: print_(text_type(err)) print_(u"")
def check_dir(path): from mutagen.mp3 import MP3 rep = Report(path) print_(u"Scanning", path) for path, dirs, files in os.walk(path): files.sort() for fn in files: if not fn.lower().endswith('.mp3'): continue ffn = os.path.join(path, fn) try: mp3 = MP3(ffn) except Exception: rep.error(ffn) else: if mp3.tags is None: rep.missing(ffn) else: rep.success(mp3.tags) print_(str(rep))
def error(*args): print_(*args, file=sys.stderr) raise SystemExit(1)
def print_help(self, file=None): print_(self.format_help(), file=file)
def list_genres(option, opt, value, parser): for i, genre in enumerate(mutagen.id3.TCON.GENRES): print_(u"%3d: %s" % (i, genre)) raise SystemExit
def list_frames(option, opt, value, parser): items = mutagen.id3.Frames.items() for name, frame in sorted(items): print_(u" --%s %s" % (name, frame.__doc__.split("\n")[0])) raise SystemExit
def main(argv): if len(argv) == 1: print_(u"Usage:", argv[0], u"directory ...") else: for path in argv[1:]: check_dir(path)
def list_frames_2_2(option, opt, value, parser): items = mutagen.id3.Frames_2_2.items() items.sort() for name, frame in items: print_(u" --%s %s" % (name, frame.__doc__.split("\n")[0])) raise SystemExit
def write_files(edits, filenames, escape): # unescape escape sequences and decode values encoded_edits = [] for frame, value in edits: if not value: continue try: frame = frame_from_fsnative(frame) except ValueError as err: print_(text_type(err), file=sys.stderr) assert isinstance(frame, str) # strip "--" frame = frame[2:] try: value = value_from_fsnative(value, escape) except ValueError as err: error(u"%s: %s" % (frame, text_type(err))) assert isinstance(value, text_type) encoded_edits.append((frame, value)) edits = encoded_edits # preprocess: # for all [frame,value] pairs in the edits list # gather values for identical frames into a list tmp = {} for frame, value in edits: if frame in tmp: tmp[frame].append(value) else: tmp[frame] = [value] # edits is now a dictionary of frame -> [list of values] edits = tmp # escape also enables escaping of the split separator if escape: string_split = split_escape else: string_split = lambda s, *args, **kwargs: s.split(*args, **kwargs) for filename in filenames: with _sig.block(): if verbose: print_(u"Writing", filename, file=sys.stderr) try: id3 = mutagen.id3.ID3(filename) except mutagen.id3.ID3NoHeaderError: if verbose: print_(u"No ID3 header found; creating a new tag", file=sys.stderr) id3 = mutagen.id3.ID3() except Exception as err: print_(str(err), file=sys.stderr) continue for (frame, vlist) in edits.items(): if frame == "POPM": for value in vlist: values = string_split(value, ":") if len(values) == 1: email, rating, count = values[0], 0, 0 elif len(values) == 2: email, rating, count = values[0], values[1], 0 else: email, rating, count = values frame = mutagen.id3.POPM( email=email, rating=int(rating), count=int(count)) id3.add(frame) elif frame == "APIC": for value in vlist: values = string_split(value, ":") # FIXME: doesn't support filenames with an invalid # encoding since we have already decoded at that point fn = values[0] if len(values) >= 2: desc = values[1] else: desc = u"cover" if len(values) >= 3: try: picture_type = int(values[2]) except ValueError: error(u"Invalid picture type: %r" % values[1]) else: picture_type = PictureType.COVER_FRONT if len(values) >= 4: mime = values[3] else: mime = mimetypes.guess_type(fn)[0] or "image/jpeg" if len(values) >= 5: error("APIC: Invalid format") encoding = get_frame_encoding(frame, desc) try: with open(fn, "rb") as h: data = h.read() except IOError as e: error(text_type(e)) frame = mutagen.id3.APIC(encoding=encoding, mime=mime, desc=desc, type=picture_type, data=data) id3.add(frame) elif frame == "COMM": for value in vlist: values = string_split(value, ":") if len(values) == 1: value, desc, lang = values[0], "", "eng" elif len(values) == 2: desc, value, lang = values[0], values[1], "eng" else: value = ":".join(values[1:-1]) desc, lang = values[0], values[-1] frame = mutagen.id3.COMM( encoding=3, text=value, lang=lang, desc=desc) id3.add(frame) elif frame == "USLT": for value in vlist: values = string_split(value, ":") if len(values) == 1: value, desc, lang = values[0], "", "eng" elif len(values) == 2: desc, value, lang = values[0], values[1], "eng" else: value = ":".join(values[1:-1]) desc, lang = values[0], values[-1] frame = mutagen.id3.USLT( encoding=3, text=value, lang=lang, desc=desc) id3.add(frame) elif frame == "UFID": for value in vlist: values = string_split(value, ":") if len(values) != 2: error(u"Invalid value: %r" % values) owner = values[0] data = values[1].encode("utf-8") frame = mutagen.id3.UFID(owner=owner, data=data) id3.add(frame) elif frame == "TXXX": for value in vlist: values = string_split(value, ":", 1) if len(values) == 1: desc, value = "", values[0] else: desc, value = values[0], values[1] frame = mutagen.id3.TXXX( encoding=3, text=value, desc=desc) id3.add(frame) elif frame == "WXXX": for value in vlist: values = string_split(value, ":", 1) if len(values) == 1: desc, value = "", values[0] else: desc, value = values[0], values[1] frame = mutagen.id3.WXXX( encoding=3, url=value, desc=desc) id3.add(frame) elif issubclass(mutagen.id3.Frames[frame], mutagen.id3.UrlFrame): frame = mutagen.id3.Frames[frame]( encoding=3, url=vlist[-1]) id3.add(frame) else: frame = mutagen.id3.Frames[frame](encoding=3, text=vlist) id3.add(frame) id3.save(filename)
def write_files(edits, filenames, escape): # unescape escape sequences and decode values encoded_edits = [] for frame, value in edits: if not value: continue try: frame = frame_from_fsnative(frame) except ValueError as err: print_(text_type(err), file=sys.stderr) assert isinstance(frame, str) # strip "--" frame = frame[2:] try: value = value_from_fsnative(value, escape) except ValueError as err: error(u"%s: %s" % (frame, text_type(err))) assert isinstance(value, text_type) encoded_edits.append((frame, value)) edits = encoded_edits # preprocess: # for all [frame,value] pairs in the edits list # gather values for identical frames into a list tmp = {} for frame, value in edits: if frame in tmp: tmp[frame].append(value) else: tmp[frame] = [value] # edits is now a dictionary of frame -> [list of values] edits = tmp # escape also enables escaping of the split separator if escape: string_split = split_escape else: string_split = lambda s, *args, **kwargs: s.split(*args, **kwargs) for filename in filenames: with _sig.block(): if verbose: print_(u"Writing", filename, file=sys.stderr) try: id3 = mutagen.id3.ID3(filename) except mutagen.id3.ID3NoHeaderError: if verbose: print_(u"No ID3 header found; creating a new tag", file=sys.stderr) id3 = mutagen.id3.ID3() except Exception as err: print_(str(err), file=sys.stderr) continue for (frame, vlist) in edits.items(): if frame == "POPM": for value in vlist: values = string_split(value, ":") if len(values) == 1: email, rating, count = values[0], 0, 0 elif len(values) == 2: email, rating, count = values[0], values[1], 0 else: email, rating, count = values frame = mutagen.id3.POPM(email=email, rating=int(rating), count=int(count)) id3.add(frame) elif frame == "APIC": for value in vlist: values = string_split(value, ":") # FIXME: doesn't support filenames with an invalid # encoding since we have already decoded at that point fn = values[0] if len(values) >= 2: desc = values[1] else: desc = u"cover" if len(values) >= 3: try: picture_type = int(values[2]) except ValueError: error(u"Invalid picture type: %r" % values[1]) else: picture_type = PictureType.COVER_FRONT if len(values) >= 4: mime = values[3] else: mime = mimetypes.guess_type(fn)[0] or "image/jpeg" if len(values) >= 5: error("APIC: Invalid format") encoding = get_frame_encoding(frame, desc) try: with open(fn, "rb") as h: data = h.read() except IOError as e: error(text_type(e)) frame = mutagen.id3.APIC(encoding=encoding, mime=mime, desc=desc, type=picture_type, data=data) id3.add(frame) elif frame == "COMM": for value in vlist: values = string_split(value, ":") if len(values) == 1: value, desc, lang = values[0], "", "eng" elif len(values) == 2: desc, value, lang = values[0], values[1], "eng" else: value = ":".join(values[1:-1]) desc, lang = values[0], values[-1] frame = mutagen.id3.COMM(encoding=3, text=value, lang=lang, desc=desc) id3.add(frame) elif frame == "UFID": for value in vlist: values = string_split(value, ":") if len(values) != 2: error(u"Invalid value: %r" % values) owner = values[0] data = values[1].encode("utf-8") frame = mutagen.id3.UFID(owner=owner, data=data) id3.add(frame) elif frame == "TXXX": for value in vlist: values = string_split(value, ":", 1) if len(values) == 1: desc, value = "", values[0] else: desc, value = values[0], values[1] frame = mutagen.id3.TXXX(encoding=3, text=value, desc=desc) id3.add(frame) elif issubclass(mutagen.id3.Frames[frame], mutagen.id3.UrlFrame): frame = mutagen.id3.Frames[frame](encoding=3, url=vlist) id3.add(frame) else: frame = mutagen.id3.Frames[frame](encoding=3, text=vlist) id3.add(frame) id3.save(filename)
def copy(src, dst, merge, write_v1=True, excluded_tags=None, verbose=False): """Returns 0 on success""" if excluded_tags is None: excluded_tags = [] try: id3 = mutagen.id3.ID3(src, translate=False) except mutagen.id3.ID3NoHeaderError: print_(u"No ID3 header found in ", src, file=sys.stderr) return 1 except Exception as err: print_(str(err), file=sys.stderr) return 1 if verbose: print_(u"File", src, u"contains:", file=sys.stderr) print_(id3.pprint(), file=sys.stderr) for tag in excluded_tags: id3.delall(tag) if merge: try: target = mutagen.id3.ID3(dst, translate=False) except mutagen.id3.ID3NoHeaderError: # no need to merge pass except Exception as err: print_(str(err), file=sys.stderr) return 1 else: for frame in id3.values(): target.add(frame) id3 = target # if the source is 2.3 save it as 2.3 if id3.version < (2, 4, 0): id3.update_to_v23() v2_version = 3 else: id3.update_to_v24() v2_version = 4 try: id3.save(dst, v1=(2 if write_v1 else 0), v2_version=v2_version) except Exception as err: print_(u"Error saving", dst, u":\n%s" % text_type(err), file=sys.stderr) return 1 else: if verbose: print_(u"Successfully saved", dst, file=sys.stderr) return 0
def printerr(*args, **kwargs): kwargs.setdefault("file", sys.stderr) print_(*args, **kwargs)
def delete_tags(filenames, v1, v2): for filename in filenames: with _sig.block(): if verbose: print_(u"deleting ID3 tag info in", filename, file=sys.stderr) mutagen.id3.delete(filename, v1, v2)