def test_scan_has_chirp_tags(self): path = os.path.join(TESTDATA, "has_chirp_tags.mp3") fast_au_file = audio_file.scan_fast(path) slow_au_file = audio_file.scan(path) self.assertEqual(path, fast_au_file.path) self.assertEqual(path, slow_au_file.path) # This volume and timestamp is extracted from the UFID. self.assertEqual(123, fast_au_file.volume) self.assertEqual(1230519180, fast_au_file.import_timestamp) self.assertEqual(3918, fast_au_file.duration_ms) self.assertEqual(slow_au_file.duration_ms, fast_au_file.duration_ms) self.assertEqual(slow_au_file.fingerprint, fast_au_file.fingerprint) # Test file contains 150 frames for a total of 137,173 bytes. self.assertEqual(150, slow_au_file.frame_count) self.assertEqual(137173, slow_au_file.frame_size) self.assertEqual(path, slow_au_file.path) # The fast scan picks up an album ID stored in the tags, # the slow scan doesn't. The test file is marked as having # an album ID of 123454321. self.assertEqual(123454321, fast_au_file.album_id) self.assertEqual(None, slow_au_file.album_id)
def scan_fast(self): """Quickly scan all MP3 files in the dropbox. Returns: A dict mapping relative file paths to either audio_file.AudioFile objects, or to None in the case of a corrupted or unreadable file. """ # Note the use of ad-hoc relativization in the path. return dict( (mp3_path[len(self._path):], audio_file.scan_fast(mp3_path)) for mp3_path in self._all_files)
def test_scan_no_chirp_tags(self): path = os.path.join(TESTDATA, "no_chirp_tags.mp3") fast_au_file = audio_file.scan_fast(path) self.assertEqual(None, fast_au_file.volume) self.assertEqual(None, fast_au_file.import_timestamp) self.assertEqual(None, fast_au_file.fingerprint) self.assertEqual(None, fast_au_file.frame_count) self.assertEqual(None, fast_au_file.frame_size) # File doesn't have a TLEN tag. self.assertEqual(None, fast_au_file.duration_ms) self.assertEqual(path, fast_au_file.path) slow_au_file = audio_file.scan(path) # The file's fingerprint is stashed in the UFID:test tag. fp = slow_au_file.mutagen_id3.get("UFID:test").data self.assertEqual(None, slow_au_file.volume) self.assertEqual(None, slow_au_file.import_timestamp) self.assertEqual(fp, slow_au_file.fingerprint) # Test file contains 150 frames for a total of 137,173 bytes. self.assertEqual(150, slow_au_file.frame_count) self.assertEqual(137173, slow_au_file.frame_size) self.assertEqual(path, slow_au_file.path)
def test_scan_fast_tag_handling(self): test_mp3 = mutagen.mp3.MP3() class MockInfo(object): pass test_mp3.info = MockInfo() test_mp3.add_tags() test_mp3.tags.add(ufid.ufid_tag(TEST_VOL, TEST_TS, TEST_FP)) test_mp3.tags.add(mutagen.id3.TLEN(text=u"11111")) test_mp3.tags.add( mutagen.id3.TXXX(desc=constants.TXXX_ALBUM_ID_DESCRIPTION, text=[u"222"])) test_mp3.tags.add( mutagen.id3.TXXX(desc=constants.TXXX_FRAME_COUNT_DESCRIPTION, text=[u"333"])) test_mp3.tags.add( mutagen.id3.TXXX(desc=constants.TXXX_FRAME_SIZE_DESCRIPTION, text=["444"])) for tag in test_mp3.tags.values(): id3_text.standardize(tag) test_mp3.info.sample_rate = 5555 test_mp3.info.bitrate = 6666 test_mp3.info.mode = 2 au_file = audio_file.scan_fast("/test/path", _read_id3_hook=lambda p: test_mp3) self.assertTrue(audio_file is not None) self.assertEqual("/test/path", au_file.path) self.assertEqual(TEST_VOL, au_file.volume) self.assertEqual(TEST_TS, au_file.import_timestamp) self.assertEqual(TEST_FP, au_file.fingerprint) self.assertEqual(11111, au_file.duration_ms) self.assertEqual(222, au_file.album_id) self.assertEqual(333, au_file.frame_count) self.assertEqual(444, au_file.frame_size) self.assertEqual(5555, au_file.mp3_header.sampling_rate_hz) self.assertEqual(6.666, au_file.mp3_header.bit_rate_kbps) self.assertEqual(2, au_file.mp3_header.channels)
def from_directory(dirpath, fast=False): """Creates Album objects from the files in a directory. Found audio files are grouped into albums based on their TALB tags. Non-audio files are silently ignored. Args: dirpath: The path to the directory to scan for audio files. fast: If True, do a fast scan when analyzing the audio files. Returns: A list of Album objects. """ by_talb = {} for basename in os.listdir(dirpath): file_path = os.path.join(dirpath, basename) # Skip anything that isn't a regular file. if not os.path.isfile(file_path): continue # Skip dotfiles if basename.startswith("."): continue # Must have mp3 as the extension. if not basename.lower().endswith(".mp3"): continue if fast: au_file = audio_file.scan_fast(file_path) else: au_file = audio_file.scan(file_path) # Silently skip anything that seems bogus. if not au_file: continue if not "TALB" in au_file.mutagen_id3: raise AlbumError("Missing TALB tag on %s" % file_path) talb = au_file.mutagen_id3["TALB"].text[0] by_talb.setdefault(talb, []).append(au_file) return [Album(all_au_files) for all_au_files in by_talb.values()]
def test_scan_fast_tag_handling(self): test_mp3 = mutagen.mp3.MP3() class MockInfo(object): pass test_mp3.info = MockInfo() test_mp3.add_tags() test_mp3.tags.add(ufid.ufid_tag(TEST_VOL, TEST_TS, TEST_FP)) test_mp3.tags.add(mutagen.id3.TLEN(text=u"11111")) test_mp3.tags.add(mutagen.id3.TXXX( desc=constants.TXXX_ALBUM_ID_DESCRIPTION, text=[u"222"])) test_mp3.tags.add(mutagen.id3.TXXX( desc=constants.TXXX_FRAME_COUNT_DESCRIPTION, text=[u"333"])) test_mp3.tags.add(mutagen.id3.TXXX( desc=constants.TXXX_FRAME_SIZE_DESCRIPTION, text=["444"])) for tag in test_mp3.tags.values(): id3_text.standardize(tag) test_mp3.info.sample_rate = 5555 test_mp3.info.bitrate = 6666 test_mp3.info.mode = 2 au_file = audio_file.scan_fast("/test/path", _read_id3_hook=lambda p: test_mp3) self.assertTrue(audio_file is not None) self.assertEqual("/test/path", au_file.path) self.assertEqual(TEST_VOL, au_file.volume) self.assertEqual(TEST_TS, au_file.import_timestamp) self.assertEqual(TEST_FP, au_file.fingerprint) self.assertEqual(11111, au_file.duration_ms) self.assertEqual(222, au_file.album_id) self.assertEqual(333, au_file.frame_count) self.assertEqual(444, au_file.frame_size) self.assertEqual(5555, au_file.mp3_header.sampling_rate_hz) self.assertEqual(6.666, au_file.mp3_header.bit_rate_kbps) self.assertEqual(2, au_file.mp3_header.channels)
def __iter__(self): """Iterator that yields a sequence of crawled MP3s. Yields: An AudioFile object. """ self._reset() yielded_size = 0 for root_path in self._all_roots: for self._current_dir, dirnames, filenames in os.walk(root_path): # We do not recursively descend into these directoryies. dirnames[:] = self._remove_ignored_directories( self._current_dir, dirnames) # If a directory filter has been specified, use it to know # when to silently skip any files in a single directory. if (self._directory_filter and not self._directory_filter(self._current_dir)): continue # Now walk across each file in this dir, yielding a # stream of AudioFile objects. for name in filenames: full_path = os.path.join(self._current_dir, name) # Skip files with the wrong sorts of names. These are # not logged. if name.startswith("."): continue if not name.lower().endswith(".mp3"): self.skipped_files.append( (full_path, "Invalid filename")) continue # Stat the file, and skip the files when that # operation fails. try: stat_obj = os.stat(full_path) except (IOError, OSError), ex: self.skipped_files.append((full_path, str(ex))) continue try: if self._fast: au_file = audio_file.scan_fast(full_path) else: au_file = audio_file.scan(full_path) except Exception, ex: # TODO(trow): Here we should really only catch # the exceptions we expect audio_file.scan and # .scan_fast to raise. self.skipped_files.append((full_path, str(ex))) logging.error("Skipping file %s: %s", full_path, str(ex)) continue if au_file is None: self.skipped_files.append( (full_path, "Not an MP3 (No tags?)")) continue # Remember this directory, then yield the AudioFile. self.directories_seen.add(self._current_dir) yield au_file
def __iter__(self): """Iterator that yields a sequence of crawled MP3s. Yields: An AudioFile object. """ self._reset() yielded_size = 0 for root_path in self._all_roots: for self._current_dir, dirnames, filenames in os.walk(root_path): # We do not recursively descend into these directoryies. dirnames[:] = self._remove_ignored_directories( self._current_dir, dirnames) # If a directory filter has been specified, use it to know # when to silently skip any files in a single directory. if (self._directory_filter and not self._directory_filter(self._current_dir)): continue # Now walk across each file in this dir, yielding a # stream of AudioFile objects. for name in filenames: full_path = os.path.join(self._current_dir, name) # Skip files with the wrong sorts of names. These are # not logged. if name.startswith("."): continue if not name.lower().endswith(".mp3"): self.skipped_files.append( (full_path, "Invalid filename")) continue # Stat the file, and skip the files when that # operation fails. try: stat_obj = os.stat(full_path) except (IOError, OSError), ex: self.skipped_files.append((full_path, str(ex))) continue try: if self._fast: au_file = audio_file.scan_fast(full_path) else: au_file = audio_file.scan(full_path) except Exception, ex: # TODO(trow): Here we should really only catch # the exceptions we expect audio_file.scan and # .scan_fast to raise. self.skipped_files.append((full_path, str(ex))) logging.error("Skipping file %s: %s", full_path, str(ex)) continue if au_file is None: self.skipped_files.append((full_path, "Not an MP3 (No tags?)")) continue # Remember this directory, then yield the AudioFile. self.directories_seen.add(self._current_dir) yield au_file