def test_tag(self):
        for frame_id, data, value, intval, info in self.DATA:
            kind = self._get_frame(frame_id)
            tag = kind._fromData(_23, 0, data)
            self.failUnless(tag.HashKey)
            self.failUnless(tag.pprint())
            self.assertEquals(value, tag)
            if 'encoding' not in info:
                self.assertRaises(AttributeError, getattr, tag, 'encoding')
            for attr, value in iteritems(info):
                t = tag
                if not isinstance(value, list):
                    value = [value]
                    t = [t]
                for value, t in izip(value, iter(t)):
                    if isinstance(value, float):
                        self.failUnlessAlmostEqual(value, getattr(t, attr), 5)
                    else:
                        self.assertEquals(value, getattr(t, attr))

                    if isinstance(intval, integer_types):
                        self.assertEquals(intval, operator.pos(t))
                    else:
                        self.assertRaises(TypeError, operator.pos, t)
Exemple #2
0
    def save(self, filething, padding=None):

        values = []
        items = sorted(self.items(), key=lambda kv: _item_sort_key(*kv))
        for key, value in items:
            try:
                values.append(self._render(key, value))
            except (TypeError, ValueError) as s:
                reraise(MP4MetadataValueError, s, sys.exc_info()[2])

        for key, failed in iteritems(self._failed_atoms):
            # don't write atoms back if we have added a new one with
            # the same name, this excludes freeform which can have
            # multiple atoms with the same key (most parsers seem to be able
            # to handle that)
            if key in self:
                assert _key2name(key) != b"----"
                continue
            for data in failed:
                values.append(Atom.render(_key2name(key), data))

        data = Atom.render(b"ilst", b"".join(values))

        # Find the old atoms.
        try:
            atoms = Atoms(filething.fileobj)
        except AtomError as err:
            reraise(error, err, sys.exc_info()[2])

        self.__save(filething.fileobj, atoms, data, padding)
Exemple #3
0
    def save(self, filename, padding=None):
        """Save the metadata to the given filename."""

        values = []
        items = sorted(self.items(), key=lambda kv: _item_sort_key(*kv))
        for key, value in items:
            try:
                values.append(self._render(key, value))
            except (TypeError, ValueError) as s:
                reraise(MP4MetadataValueError, s, sys.exc_info()[2])

        for key, failed in iteritems(self._failed_atoms):
            # don't write atoms back if we have added a new one with
            # the same name, this excludes freeform which can have
            # multiple atoms with the same key (most parsers seem to be able
            # to handle that)
            if key in self:
                assert _key2name(key) != b"----"
                continue
            for data in failed:
                values.append(Atom.render(_key2name(key), data))

        data = Atom.render(b"ilst", b"".join(values))

        # Find the old atoms.
        with open(filename, "rb+") as fileobj:
            try:
                atoms = Atoms(fileobj)
            except AtomError as err:
                reraise(error, err, sys.exc_info()[2])

            self.__save(fileobj, atoms, data, padding)
  def has_metadata_changed(self, mutagen_file, track, is_complex_type):
    if is_complex_type:
      # If you're dealing with any of these complex types, it's wayyyy easier to
      # simply just rewrite the metadata every single time. Just go to the disk.
      return True

    left_keys = mutagen_file.keys()
    right_keys = track.keys()

    left_keys_len = len(left_keys)
    # Note that the '@' field is virtual, so we won't count that.
    right_keys_len = len(right_keys) - 1

    # If the number of metadata fields if different, the metadata has changed.
    if left_keys_len != right_keys_len:
      return True

    marshalled_right_keys = [self.marshal_mutagen_key(
                                 mutagen_file, key, is_complex_type)
                             for key in right_keys if key != '@']

    # If any of the fields in the right are not in the left, it's changed.
    for each in marshalled_right_keys:
      # Mutagen can lowercase the data and Foobar can still read it.
      if each not in left_keys and each.lower() not in left_keys:
        return True

    # If any of the field values have changed (or changed types), it's changed.
    for key, value in iteritems(track):
      if key != '@':
        marshalled_key = self.marshal_mutagen_key(
            mutagen_file, key, is_complex_type)

        before = mutagen_file[marshalled_key]

        if isinstance(before, (list, tuple)):
          # Mutagen usually gives us a singleton list.
          if len(before) == 1:
            before = before[0]
            if before != value:
              # This single field has changed.
              return True
          else:
            if isinstance(value, (list, tuple)):
              if len(before) == len(value):
                for i, each in enumerate(before):
                  if each != value[i]:
                    # The values or order of values have changed.
                    return True
              else:
                # This field has changed types or length.
                return True
        else:
          if before != value:
            return True

    return False
Exemple #5
0
 def pprint(self):
     values = []
     for key, value in iteritems(self):
         if key == b"covr":
             values.append("%r=%s" % (key, ", ".join(
                 [("[%d bytes of data]" % len(data)) for data in value])))
         elif isinstance(value, list):
             values.append("%r=%s" % (key, " / ".join(map(text_type, value))))
         else:
             values.append("%r=%s" % (key, value))
     return "\n".join(values)
 def pprint(self):
     values = []
     for key, value in iteritems(self):
         if not isinstance(key, text_type):
             key = key.decode("latin-1")
         if key == "covr":
             values.append("%s=%s" % (key, ", ".join(
                 ["[%d bytes of data]" % len(data) for data in value])))
         elif isinstance(value, list):
             for v in value:
                 values.append("%s=%r" % (key, v))
         else:
             values.append("%s=%r" % (key, value))
     return "\n".join(values)
Exemple #7
0
 def pprint(self):
     values = []
     for key, value in iteritems(self):
         if not isinstance(key, text_type):
             key = key.decode("latin-1")
         if key == "covr":
             values.append("%s=%s" % (key, ", ".join(
                 ["[%d bytes of data]" % len(data) for data in value])))
         elif isinstance(value, list):
             for v in value:
                 values.append("%s=%r" % (key, v))
         else:
             values.append("%s=%r" % (key, value))
     return "\n".join(values)
Exemple #8
0
 def pprint(self):
     values = []
     for key, value in iteritems(self):
         if key == b"covr":
             values.append(
                 "%r=%s" %
                 (key, ", ".join([("[%d bytes of data]" % len(data))
                                  for data in value])))
         elif isinstance(value, list):
             values.append("%r=%s" %
                           (key, " / ".join(map(text_type, value))))
         else:
             values.append("%r=%s" % (key, value))
     return "\n".join(values)
    def save(self, filename):
        """Save the metadata to the given filename."""

        values = []
        items = sorted(self.items(), key=self._key_sort)
        for key, value in items:
            atom_name = _key2name(key)[:4]
            if atom_name in self.__atoms:
                render_func = self.__atoms[atom_name][1]
            else:
                render_func = type(self).__render_text

            try:
                if value:
                    values.append(render_func(self, key, value))
            except (TypeError, ValueError) as s:
                reraise(MKVMetadataValueError, s, sys.exc_info()[2])

        for key, failed in iteritems(self._failed_atoms):
            # don't write atoms back if we have added a new one with
            # the same name, this excludes freeform which can have
            # multiple atoms with the same key (most parsers seem to be able
            # to handle that)
            if key in self:
                assert _key2name(key) != b"----"
                continue
            for data in failed:
                values.append(Atom.render(_key2name(key), data))

        data = Atom.render(b"ilst", b"".join(values))

        # Find the old atoms.
        with open(filename, "rb+") as fileobj:
            try:
                atoms = Atoms(fileobj)
            except AtomError as err:
                reraise(error, err, sys.exc_info()[2])

            try:
                # path = atoms.path(b"moov", b"udta", b"meta", b"ilst")
                path = atoms.path(b"udta", b"meta", b"ilst")
            except KeyError:
                self.__save_new(fileobj, atoms, data)
            else:
                self.__save_existing(fileobj, atoms, path, data)
    def save(self, filename):
        """Save the metadata to the given filename."""

        values = []
        items = sorted(self.items(), key=self._key_sort)
        for key, value in items:
            atom_name = _key2name(key)[:4]
            if atom_name in self.__atoms:
                render_func = self.__atoms[atom_name][1]
            else:
                render_func = type(self).__render_text

            try:
                if value:
                    values.append(render_func(self, key, value))
            except (TypeError, ValueError) as s:
                reraise(MP4MetadataValueError, s, sys.exc_info()[2])

        for key, failed in iteritems(self._failed_atoms):
            # don't write atoms back if we have added a new one with
            # the same name, this excludes freeform which can have
            # multiple atoms with the same key (most parsers seem to be able
            # to handle that)
            if key in self:
                assert _key2name(key) != b"----"
                continue
            for data in failed:
                values.append(Atom.render(_key2name(key), data))

        data = Atom.render(b"ilst", b"".join(values))

        # Find the old atoms.
        with open(filename, "rb+") as fileobj:
            try:
                atoms = Atoms(fileobj)
            except AtomError as err:
                reraise(error, err, sys.exc_info()[2])

            try:
                path = atoms.path(b"moov", b"udta", b"meta", b"ilst")
            except KeyError:
                self.__save_new(fileobj, atoms, data)
            else:
                self.__save_existing(fileobj, atoms, path, data)
Exemple #11
0
    def pprint(self):
        def to_line(key, value):
            assert isinstance(key, text_type)
            if isinstance(value, text_type):
                return u"%s=%s" % (key, value)
            return u"%s=%r" % (key, value)

        values = []
        for key, value in sorted(iteritems(self)):
            if not isinstance(key, text_type):
                key = key.decode("latin-1")
            if key == "covr":
                values.append(u"%s=%s" % (key, u", ".join(
                    [u"[%d bytes of data]" % len(data) for data in value])))
            elif isinstance(value, list):
                for v in value:
                    values.append(to_line(key, v))
            else:
                values.append(to_line(key, value))
        return u"\n".join(values)
Exemple #12
0
    def pprint(self):

        def to_line(key, value):
            assert isinstance(key, text_type)
            if isinstance(value, text_type):
                return u"%s=%s" % (key, value)
            return u"%s=%r" % (key, value)

        values = []
        for key, value in sorted(iteritems(self)):
            if not isinstance(key, text_type):
                key = key.decode("latin-1")
            if key == "covr":
                values.append(u"%s=%s" % (key, u", ".join(
                    [u"[%d bytes of data]" % len(data) for data in value])))
            elif isinstance(value, list):
                for v in value:
                    values.append(to_line(key, v))
            else:
                values.append(to_line(key, value))
        return u"\n".join(values)
Exemple #13
0
 def pprint(self):
     return "\n".join(("%s=%s" % (k, v)) for k, v in iteritems(self))
def configure_id3_ext():
  """
  Configures Mutagen to handle ID3 tags in exactly the same way that foobar2000
  does it for compatibility, and also for simplicity so that we can use the
  EasyID3 interface to treat all file types the same (except probably MP4).
  """

  global is_configured

  if is_configured:
    return

  # First, we need to configure all the extended text frames.
  for frameid, key in iteritems({
      'TPE2': 'album artist',
      'TPE2': 'albumartist',
      'TSO2': 'albumartistsortorder',
      'TSOA': 'albumsortorder',
      'TSOP': 'artistsortorder',
      'TPE2': 'band',
      'TSOC': 'composersortorder',
      'TIT1': 'content group',
      'TENC': 'encoded by',
      'TSSE': 'encoding settings',
      'TKEY': 'initial key',
      'TCMP': 'itunescompilation',
      'TMED': 'media type',
      'TOAL': 'original album',
      'TOPE': 'original artist',
      'TOWN': 'owner',
      'TPUB': 'publisher',
      'WPUB': 'publisher url',
      'TRSN': 'radio station',
      'TRSO': 'radio station owner',
      'TDRL': 'release date',
      'TPE4': 'remixed by',
      'TSST': 'set subtitle',
      'TIT3': 'subtitle',
      'TSOT': 'titlesortorder',
      'TEXT': 'writer',
  }):
    EasyID3.RegisterTextKey(key, frameid)

  # And now we need to configure URL frames.
  for frameid, key in iteritems({
      'WOAR': 'artist webpage url',
      'WCOM': 'commercial information url',
      'WCOP': 'copyright url',
      'WOAF': 'file webpage url',
      'WORS': 'internet radio webpage url',
      'WPAY': 'payment url',
      'WOAS': 'source webpage url',
  }):
    configure_url_frame(key, frameid)

  # And now to handle text frames.
  # NOTE: Foobar handles "RECORDING DATES" differently depending on whether you
  # are dealing with ID3 2.3 or 2.4 tags.  Since we currently only support using
  # 2.4 tags, we'll do it this way (in a TXXX frame called "recording dates",
  # verified with Foobar 1.3.7), but in 2.3 (and perhaps earlier versions of
  # ID3), it will store this info instead in TRDA frames.
  for desc, key in iteritems({
      'recording dates': 'recording dates',
  }):
    EasyID3.RegisterTXXXKey(key, desc)

  # Override Mutagen's default behavior for ReplayGain, since Foobar handles it
  # differently. We will still write the RVA2 frame, but we'll write the
  # ReplayGain info to TXXX frames as well.
  del(EasyID3.Get['replaygain_*_gain'])
  del(EasyID3.Get['replaygain_*_peak'])
  del(EasyID3.Set['replaygain_*_gain'])
  del(EasyID3.Set['replaygain_*_peak'])
  del(EasyID3.Delete['replaygain_*_gain'])
  del(EasyID3.Delete['replaygain_*_peak'])

  EasyID3.RegisterKey('replaygain_*_gain',
      gain_get_with_txxx, gain_set_with_txxx, gain_delete_with_txxx,
      peakgain_list)
  EasyID3.RegisterKey('replaygain_*_peak',
      peak_get_with_txxx, peak_set_with_txxx, peak_delete_with_txxx)

  # And for whatever unknown frames exist out there, we will just make them TXXX
  # comments, just to be completely sure we're copying EVERYTHING.
  EasyID3.SetFallback = comment_txxx_set_fallback

  # TODO(dremelofdeath): Support unsynced lyrics -- this can be complicated due
  # to the fact that ID3 supports different encodings and languages for this
  # frame, and we will have to detect both of those when converting M-TAGS,
  # since M-TAGS is always unicode, and I'm not sure how Foobar stores the
  # language attribute of this frame. (I also don't use this field.)
  #    'USLT': 'unsynced lyrics',

  is_configured = True
Exemple #15
0
 def pprint(self):
     return "\n".join(("%s=%s" % (k, v)) for k, v in iteritems(self))
  def really_handle_metadata(
      self, filename, mutagen_file, track, is_new_file, silent):
    is_new_instance = False

    if mutagen_file is None or not is_mutagen_file(mutagen_file):
      is_new_instance = True
      mutagen_file = File(filename, easy=True)

    is_complex_type = isinstance(mutagen_file, (
        ID3FileType, EasyID3FileType, EasyMP4, EasyMP3, MP3, MP4,
        EasyTrueAudio, TrueAudio))

    changed = is_new_file or self.has_metadata_changed(
        mutagen_file, track, is_complex_type)

    if is_new_file or changed:
      if self.progress and not silent:
        self.printer.update_status('Updating metadata')

      try:
        if not self.args.dry_run:
          self.maybe_clear_existing_metadata(
              filename, mutagen_file, is_new_file)

        complex_discnumber = None
        complex_totaldiscs = None
        complex_tracknumber = None
        complex_totaltracks = None

        for key, value in iteritems(track):
          if key == '@':
            continue

          mutagen_key = self.marshal_mutagen_key(
              mutagen_file, key, is_complex_type)

          if is_complex_type:
            if mutagen_key == 'discnumber':
              complex_discnumber = value
              continue
            elif mutagen_key == 'totaldiscs':
              complex_totaldiscs = value
              continue
            elif mutagen_key == 'tracknumber':
              complex_tracknumber = value
              continue
            elif mutagen_key == 'totaltracks':
              complex_totaltracks = value
              continue

          mutagen_file[mutagen_key] = value

        if is_complex_type:
          if complex_totaldiscs:
            if complex_discnumber:
              complex_value = complex_discnumber + '/' + complex_totaldiscs
              mutagen_file['discnumber'] = complex_value
            else:
              mutagen_file['totaldiscs'] = complex_totaldiscs
          elif complex_discnumber:
            mutagen_file['discnumber'] = complex_discnumber

          if complex_totaltracks:
            if complex_tracknumber:
              complex_value = complex_tracknumber + '/' + complex_totaltracks
              mutagen_file['tracknumber'] = complex_value
            else:
              mutagen_file['totaltracks'] = complex_totaltracks
          elif complex_tracknumber:
            mutagen_file['tracknumber'] = complex_tracknumber

        if not self.args.dry_run and not is_new_instance:
          self.handle_metadata_write(filename, mutagen_file, is_new_file)
      except UnwritableMetadataException:
        self.on_unwritable_metadata(filename)

    return changed
Exemple #17
0
 def test_items(self):
     self.failUnlessEqual(
         list(self.fdict.items()), list(self.rdict.items()))
     self.failUnlessEqual(
         list(iteritems(self.fdict)), list(iteritems(self.rdict)))
Exemple #18
0
 def test_items(self):
     self.failUnlessEqual(list(self.fdict.items()),
                          list(self.rdict.items()))
     self.failUnlessEqual(list(iteritems(self.fdict)),
                          list(iteritems(self.rdict)))