def test_basic(self):
     test_vol = 11
     test_ts_human = "20090102-030405"
     test_ts = timestamp.parse_human_readable(test_ts_human)
     test_fp = "1234" * 10
     # The UFID prefix should contain the volume and timestamp info.
     self.assertEqual(
         "vol0b/%s/" % test_ts_human,  # 0b = 11
         ufid.ufid_prefix(test_vol, test_ts))
     # The UFID should equal the UFID prefix + the fingerprint.
     test_ufid = ufid.ufid(test_vol, test_ts, test_fp)
     self.assertEqual(
         ufid.ufid_prefix(test_vol, test_ts) + test_fp, test_ufid)
     # We should be able to make a tag too.
     test_tag = ufid.ufid_tag(test_vol, test_ts, test_fp)
     self.assertEqual("UFID", test_tag.FrameID)
     self.assertEqual(constants.UFID_OWNER_IDENTIFIER, test_tag.owner)
     self.assertEqual(test_ufid, test_tag.data)
     # Make sure we can parse information back out of the test UFID.
     vol, ts, fp = ufid.parse(test_ufid)
     self.assertEqual(test_vol, vol)
     self.assertEqual(test_ts, ts)
     self.assertEqual(test_fp, fp)
     # Raise ValueError if we try to parse a bad UFID.
     self.assertRaises(ValueError, ufid.parse, "bad")
     self.assertRaises(ValueError, ufid.parse,
                       "vol01/20091399-666666/" + "1" * 40)
     self.assertRaises(ValueError, ufid.parse,
                       "vol01/20991001-123456" + "1" * 40)
def scan_fast(path, _read_id3_hook=None):
    """Quickly produce an AudioFile object for the file at 'path'.

    This function avoids expensive calculations by assuming that the
    file is fully and correctly tagged.  It never sets the 'payload'
    field.  The 'duration_ms' field will be set based on the TLEN tag,
    if present.  The 'frame_count' and 'frame_size' will be set based
    on the CHIRP-specific TXXX tags, if present.

    Args:
      path: The path to an MP3 file.
      _read_id3_hook: An optional callable that takes a path and
        returns mutagen ID3 data.  Passing in None (the default) uses
        a default implementation.  This argument should only be used
        for testing.

    Returns:
      An AudioFile object describing the file at 'path', or None if it does
      not appear to be a valid MPEG file.
    """
    au_file = AudioFile()
    au_file.path = path
    au_file.mutagen_id3 = (_read_id3_hook or _get_mp3)(path)
    if au_file.mutagen_id3 is None:
        return None

    this_ufid = au_file.mutagen_id3.get(constants.MUTAGEN_UFID_KEY)
    if this_ufid:
        try:
            au_file.volume, au_file.import_timestamp, au_file.fingerprint = (
                ufid.parse(this_ufid.data))
        except ValueError:
            pass

    # Note we use the fact that mutagen_id3 is actually a mutagen.MP3 object.
    au_file.mp3_header = mp3_header.from_mutagen(au_file.mutagen_id3)

    # We assume that TLEN is accurate.  Note that it would make sense to
    # use au_file.mutagen_id3.info.length for this, but mutagen's length
    # computation is not reliable.
    au_file.duration_ms = _tag_to_int(au_file, "TLEN")

    # Try to get the frame_size and frame_count from the tags.  Again,
    # we assume these are accurate.
    au_file.frame_count = _tag_to_int(au_file, constants.TXXX_FRAME_COUNT_KEY)
    au_file.frame_size = _tag_to_int(au_file, constants.TXXX_FRAME_SIZE_KEY)

    # Try to pull the album ID out of a a tag.
    au_file.album_id = _tag_to_int(au_file, constants.TXXX_ALBUM_ID_KEY)

    return au_file
Exemple #3
0
def scan_fast(path, _read_id3_hook=None):
    """Quickly produce an AudioFile object for the file at 'path'.

    This function avoids expensive calculations by assuming that the
    file is fully and correctly tagged.  It never sets the 'payload'
    field.  The 'duration_ms' field will be set based on the TLEN tag,
    if present.  The 'frame_count' and 'frame_size' will be set based
    on the CHIRP-specific TXXX tags, if present.

    Args:
      path: The path to an MP3 file.
      _read_id3_hook: An optional callable that takes a path and
        returns mutagen ID3 data.  Passing in None (the default) uses
        a default implementation.  This argument should only be used
        for testing.

    Returns:
      An AudioFile object describing the file at 'path', or None if it does
      not appear to be a valid MPEG file.
    """
    au_file = AudioFile()
    au_file.path = path
    au_file.mutagen_id3 = (_read_id3_hook or _get_mp3)(path)
    if au_file.mutagen_id3 is None:
        return None

    this_ufid = au_file.mutagen_id3.get(constants.MUTAGEN_UFID_KEY)
    if this_ufid:
        try:
            au_file.volume, au_file.import_timestamp, au_file.fingerprint = (
                ufid.parse(this_ufid.data))
        except ValueError:
            pass

    # Note we use the fact that mutagen_id3 is actually a mutagen.MP3 object.
    au_file.mp3_header = mp3_header.from_mutagen(au_file.mutagen_id3)

    # We assume that TLEN is accurate.  Note that it would make sense to
    # use au_file.mutagen_id3.info.length for this, but mutagen's length
    # computation is not reliable.
    au_file.duration_ms = _tag_to_int(au_file, "TLEN")

    # Try to get the frame_size and frame_count from the tags.  Again,
    # we assume these are accurate.
    au_file.frame_count = _tag_to_int(au_file, constants.TXXX_FRAME_COUNT_KEY)
    au_file.frame_size = _tag_to_int(au_file, constants.TXXX_FRAME_SIZE_KEY)

    # Try to pull the album ID out of a a tag.
    au_file.album_id = _tag_to_int(au_file, constants.TXXX_ALBUM_ID_KEY)

    return au_file
 def test_basic(self):
     test_vol = 11
     test_ts_human = "20090102-030405"
     test_ts = timestamp.parse_human_readable(test_ts_human)
     test_fp = "1234" * 10
     # The UFID prefix should contain the volume and timestamp info.
     self.assertEqual("vol0b/%s/" % test_ts_human, ufid.ufid_prefix(test_vol, test_ts))  # 0b = 11
     # The UFID should equal the UFID prefix + the fingerprint.
     test_ufid = ufid.ufid(test_vol, test_ts, test_fp)
     self.assertEqual(ufid.ufid_prefix(test_vol, test_ts) + test_fp, test_ufid)
     # We should be able to make a tag too.
     test_tag = ufid.ufid_tag(test_vol, test_ts, test_fp)
     self.assertEqual("UFID", test_tag.FrameID)
     self.assertEqual(constants.UFID_OWNER_IDENTIFIER, test_tag.owner)
     self.assertEqual(test_ufid, test_tag.data)
     # Make sure we can parse information back out of the test UFID.
     vol, ts, fp = ufid.parse(test_ufid)
     self.assertEqual(test_vol, vol)
     self.assertEqual(test_ts, ts)
     self.assertEqual(test_fp, fp)
     # Raise ValueError if we try to parse a bad UFID.
     self.assertRaises(ValueError, ufid.parse, "bad")
     self.assertRaises(ValueError, ufid.parse, "vol01/20091399-666666/" + "1" * 40)
     self.assertRaises(ValueError, ufid.parse, "vol01/20991001-123456" + "1" * 40)
Exemple #5
0
def find_tags_errors(au_file):
    """Return a list of errors found in a file's ID3 tags.

    Args:
      au_file: An AudioFile object.

    Returns:
      A list of human-readable strings describing errors or
      inconsistencies found in the AudioFile object's ID3 tags,
      or the empty list if no errors are found.
    """
    errors = []

    # Make sure all required tags are there.
    for tag in constants.ID3_TAG_REQUIRED:
        if tag not in au_file.mutagen_id3:
            errors.append(ERROR_TAG_MISSING_REQUIRED + tag)

    # Checks that are not really tag-specific.
    for tag in au_file.mutagen_id3.itervalues():
        # Make sure all tags are on the whitelist.
        if (tag.FrameID not in constants.ID3_TAG_WHITELIST
                and tag.HashKey not in constants.ID3_TAG_WHITELIST):
            errors.append(ERROR_TAG_NOT_WHITELISTED + tag.FrameID)
        # Make sure all text tags have the correct encoding.
        if tag.FrameID.startswith("T"):
            encoding = getattr(tag, "encoding", "missing")
            if (encoding != "missing"
                    and encoding != constants.DEFAULT_ID3_TEXT_ENCODING):
                errors.append(ERROR_TAG_WRONG_ENCODING + tag.FrameID)

    # Check that numeric tags are actually numeric.
    for frame_id in ("TBPM", "TLEN", "TORY", "TYER"):
        tag = au_file.mutagen_id3.get(frame_id)
        if tag and not (len(tag.text) == 1 and tag.text[0].isdigit()):
            errors.append(ERROR_NUMERIC_MALFORMED + frame_id)

    # Check that TFLT contains a whitelisted file type.
    this_tflt = au_file.mutagen_id3.get("TFLT")
    if (this_tflt and not (len(this_tflt.text) == 1
                           and this_tflt.text[0] in constants.TFLT_WHITELIST)):
        errors.append(ERROR_TFLT_NON_WHITELISTED)

    # Check that TLEN contains the correct length.
    this_tlen = au_file.mutagen_id3.get("TLEN")
    if (this_tlen and not (len(this_tlen.text) == 1
                           and str(au_file.duration_ms) == this_tlen.text[0])):
        errors.append(ERROR_TLEN_INCORRECT)

    # Check that the TXXX with the frame count is correct.
    this_fc = au_file.mutagen_id3.get(constants.TXXX_FRAME_COUNT_KEY)
    if (this_fc and not (len(this_fc.text) == 1
                         and str(au_file.frame_count) == this_fc.text[0])):
        errors.append(ERROR_TXXX_FRAME_COUNT_INCORRECT)

    # Check that the TXXX with the frame size is correct.
    this_fs = au_file.mutagen_id3.get(constants.TXXX_FRAME_SIZE_KEY)
    if (this_fs and not (len(this_fs.text) == 1
                         and str(au_file.frame_size) == this_fs.text[0])):
        errors.append(ERROR_TXXX_FRAME_SIZE_INCORRECT)

    # Check that TOWN contains the expected string.
    this_town = au_file.mutagen_id3.get("TOWN")
    if (this_town
            and not (len(this_town.text) == 1
                     and this_town.text[0] == constants.TOWN_FILE_OWNER)):
        errors.append(ERROR_TOWN_INCORRECT)

    # Check that the TPE tags contain known artists.
    for tag in au_file.mutagen_id3.itervalues():
        if tag.FrameID.startswith("TPE"):
            for txt in tag.text:
                if not artists.is_standardized(txt):
                    errors.append(ERROR_TPE_NONSTANDARD + txt)

    # Check that TRCK and TPOS contains a valid order-numbering in our
    # standard form.
    for this_tag in (au_file.mutagen_id3.get("TPOS"),
                     au_file.mutagen_id3.get("TRCK")):
        if this_tag and not (len(this_tag.text) == 1
                             and order.is_archival(this_tag.text[0])):
            errors.append(
                "%s%s %s" %
                (ERROR_ORDER_MALFORMED, this_tag.FrameID, this_tag.text))

    # Check the UFID.
    this_ufid = au_file.mutagen_id3.get(constants.MUTAGEN_UFID_KEY)
    if this_ufid:
        try:
            vol, ts, fp = ufid.parse(this_ufid.data)
            if au_file.volume != vol:
                errors.append(ERROR_UFID_BAD_VOLUME)
            if au_file.import_timestamp != ts:
                errors.append(ERROR_UFID_BAD_TIMESTAMP)
            if au_file.fingerprint != fp:
                errors.append(ERROR_UFID_BAD_FINGERPRINT)
        except ValueError, ex:
            errors.append(ERROR_UFID_BAD_MALFORMED)
Exemple #6
0
def find_tags_errors(au_file):
    """Return a list of errors found in a file's ID3 tags.

    Args:
      au_file: An AudioFile object.

    Returns:
      A list of human-readable strings describing errors or
      inconsistencies found in the AudioFile object's ID3 tags,
      or the empty list if no errors are found.
    """
    errors = []

    # Make sure all required tags are there.
    for tag in constants.ID3_TAG_REQUIRED:
        if tag not in au_file.mutagen_id3:
            errors.append(ERROR_TAG_MISSING_REQUIRED + tag)

    # Checks that are not really tag-specific.
    for tag in au_file.mutagen_id3.itervalues():
        # Make sure all tags are on the whitelist.
        if (tag.FrameID not in constants.ID3_TAG_WHITELIST
            and tag.HashKey not in constants.ID3_TAG_WHITELIST):
            errors.append(ERROR_TAG_NOT_WHITELISTED + tag.FrameID)
        # Make sure all text tags have the correct encoding.
        if tag.FrameID.startswith("T"):
            encoding = getattr(tag, "encoding", "missing")
            if (encoding != "missing"
                and encoding != constants.DEFAULT_ID3_TEXT_ENCODING):
                errors.append(ERROR_TAG_WRONG_ENCODING + tag.FrameID)

    # Check that numeric tags are actually numeric.
    for frame_id in ("TBPM", "TLEN", "TORY", "TYER"):
        tag = au_file.mutagen_id3.get(frame_id)
        if tag and not (len(tag.text) == 1 and tag.text[0].isdigit()):
            errors.append(ERROR_NUMERIC_MALFORMED + frame_id)

    # Check that TFLT contains a whitelisted file type.
    this_tflt = au_file.mutagen_id3.get("TFLT")
    if (this_tflt
        and not (len(this_tflt.text) == 1
                 and this_tflt.text[0] in constants.TFLT_WHITELIST)):
        errors.append(ERROR_TFLT_NON_WHITELISTED)

    # Check that TLEN contains the correct length.
    this_tlen = au_file.mutagen_id3.get("TLEN")
    if (this_tlen
        and not (len(this_tlen.text) == 1
                 and str(au_file.duration_ms) == this_tlen.text[0])):
        errors.append(ERROR_TLEN_INCORRECT)

    # Check that the TXXX with the frame count is correct.
    this_fc = au_file.mutagen_id3.get(constants.TXXX_FRAME_COUNT_KEY)
    if (this_fc
        and not (len(this_fc.text) == 1
                 and str(au_file.frame_count) == this_fc.text[0])):
        errors.append(ERROR_TXXX_FRAME_COUNT_INCORRECT)

    # Check that the TXXX with the frame size is correct.
    this_fs = au_file.mutagen_id3.get(constants.TXXX_FRAME_SIZE_KEY)
    if (this_fs
        and not (len(this_fs.text) == 1
                 and str(au_file.frame_size) == this_fs.text[0])):
        errors.append(ERROR_TXXX_FRAME_SIZE_INCORRECT)

    # Check that TOWN contains the expected string.
    this_town = au_file.mutagen_id3.get("TOWN")
    if (this_town
        and not (len(this_town.text) == 1
                 and this_town.text[0] == constants.TOWN_FILE_OWNER)):
        errors.append(ERROR_TOWN_INCORRECT)

    # Check that the TPE tags contain known artists.
    for tag in au_file.mutagen_id3.itervalues():
        if tag.FrameID.startswith("TPE"):
            for txt in tag.text:
                if not artists.is_standardized(txt):
                    errors.append(ERROR_TPE_NONSTANDARD + txt)

    # Check that TRCK and TPOS contains a valid order-numbering in our
    # standard form.
    for this_tag in (au_file.mutagen_id3.get("TPOS"),
                     au_file.mutagen_id3.get("TRCK")):
        if this_tag and not (len(this_tag.text) == 1
                             and order.is_archival(this_tag.text[0])):
            errors.append("%s%s %s" % (ERROR_ORDER_MALFORMED,
                                       this_tag.FrameID,
                                       this_tag.text))

    # Check the UFID.
    this_ufid = au_file.mutagen_id3.get(constants.MUTAGEN_UFID_KEY)
    if this_ufid:
        try:
            vol, ts, fp = ufid.parse(this_ufid.data)
            if au_file.volume != vol:
                errors.append(ERROR_UFID_BAD_VOLUME)
            if au_file.import_timestamp != ts:
                errors.append(ERROR_UFID_BAD_TIMESTAMP)
            if au_file.fingerprint != fp:
                errors.append(ERROR_UFID_BAD_FINGERPRINT)
        except ValueError, ex:
            errors.append(ERROR_UFID_BAD_MALFORMED)