def convert(flac, output_dir, hidden): """Convert FLAC files.""" for path in flac: info = FileInfo(path) summary = info.summary if not summary.parse_ok or not summary.cuesheet: continue album_tags = info.tags.album_tags() if album_tags.get('HIDE') == 'true' and not hidden: continue if 'ARTIST' not in album_tags or 'ALBUM' not in album_tags: continue tracks = fc.prepare_tracks(info, output_dir, 'ogg') if not tracks: continue if any(map(lambda t: os.path.exists(t.path), tracks)): continue click.echo('{} {}'.format(summary, path)) try: click.echo('- Decoding tracks') tempdir = fc.decode_tracks(info) click.echo('- Encoding tracks') fc.encode_tracks(tracks, tempdir, 'ogg') except fc.ConversionError as e: click.echo('ERROR {}'.format(e)) click.get_current_context().exit(1) front = info.get_picture(FRONT_COVER_TYPE) if front is not None: dst_base = os.path.dirname(tracks[0].path) fc.export_cover(front, dst_base)
def test_export_cover(self, datadir): """Test the export_cover function.""" info = FileInfo(datadir / 'tagged.flac') front = info.get_picture(3) fc.export_cover(front, str(datadir)) path = Path(datadir / 'cover.jpg') assert path.is_file()
def test_init_empty(self, datadir): """Call the constructor with an empty FLAC file.""" file = FileInfo(datadir / 'empty.flac') assert file.parse_ok is True assert file.parse_exception is None assert file.streaminfo is not None assert file.cuesheet is None assert file.tags is not None assert file.pictures() is not None
def test_init_invalid(self, datadir): """Call the constructor with an invalid FLAC file.""" file = FileInfo(datadir / 'invalid.flac') assert file.parse_ok is False assert file.parse_exception is not None assert file.streaminfo is None assert file.cuesheet is None assert file.tags is None assert file.pictures() is None
def test_set_picture(self, datadir): """Test setting a picture.""" file = FileInfo(datadir / 'empty.flac') with open(str(datadir / 'cover.png'), 'rb') as cover: data = cover.read() picture = parse_picture(data, 3) assert file.set_picture(picture) is True assert file.set_picture(picture) is False file.update() self.assert_picture(file, 3, 'image/png', 128, 128, 24, data)
def tag(flac, mbid, hide, unhide, hide_track, unhide_track): """Tag FLAC files. Files with a cue sheet but no album/track-level tags will be tagged using metadata from MusicBrainz. """ mb = MusicBrainz() for path in flac: info = FileInfo(path) summary = info.summary if not summary.parse_ok or not summary.cuesheet: continue tagged = summary.album_tags or summary.track_tags album_edit = hide or unhide track_edit = hide_track or unhide_track if tagged and not (album_edit or track_edit): continue click.echo('{} {}'.format(summary, path)) album_changed = False track_changed = False # Query MusicBrainz if not tagged: try: if mbid is None: release = find_release(mb, info) else: release = mb.release_by_id(mbid, info.cuesheet) if release is None: continue original_date = mb.first_release_date(release['group-id']) except MusicBrainzError: click.echo('- Error while querying MusicBrainz') continue album_changed |= update_album_tags(info, release, original_date) track_changed |= update_track_tags(info, release) # Hide or unhide album tags = info.tags.album_tags() if hide: tags['HIDE'] = 'true' if unhide: tags.pop('HIDE', None) album_changed |= info.tags.update_album(tags) # Hide or unhide tracks for number in hide_track: tags = info.tags.track_tags(number) tags['HIDE'] = 'true' track_changed |= info.tags.update_track(number, tags) for number in unhide_track: tags = info.tags.track_tags(number) tags.pop('HIDE', None) track_changed |= info.tags.update_track(number, tags) # Save any changes if album_changed or track_changed: info.update()
def test_init(self, datadir): """Call the constructor with a valid FLAC file.""" file = FileInfo(datadir / 'test.flac') assert file.parse_ok is True assert file.parse_exception is None assert file.streaminfo is not None assert file.cuesheet is not None assert file.tags is not None assert file.pictures() is not None streaminfo = file.streaminfo assert streaminfo.channels == 2 assert streaminfo.sample_bits == 16 assert streaminfo.sample_rate == 44100 assert streaminfo.sample_count == 132300
def test_hide(self, datadir): """Test the --hide option.""" path = datadir / 'tagged.flac' runner = CliRunner() result = runner.invoke(flackup, ['tag', '--hide', str(path)]) info = FileInfo(path) assert result.exit_code == 0 assert info.tags.album_tags().get('HIDE') == 'true'
def test_unhide_track(self, datadir): """Test the --unhide-track option.""" path = datadir / 'tagged.flac' runner = CliRunner() result = runner.invoke(flackup, ['tag', '-T', 3, str(path)]) info = FileInfo(path) assert result.exit_code == 0 assert info.tags.track_tags(3).get('HIDE') is None
def test_prepare_tracks(self, datadir): """Test the prepare_tracks function.""" info = FileInfo(datadir / 'tagged.flac') tracks = fc.prepare_tracks(info, str(datadir), 'ogg') assert len(tracks) == 2 for track in tracks: assert len(track.tags) == 5 assert track.path.endswith('.ogg')
def test_untagged(self, datadir): """Test reading an untagged FLAC file.""" file = FileInfo(datadir / 'test.flac') tags = file.tags assert tags.album_tags() == {} track_numbers = [t.number for t in file.cuesheet.audio_tracks] for number in track_numbers: assert tags.track_tags(number) == {}
def test_empty(self, datadir): """Test with an empty FLAC file.""" file = FileInfo(datadir / 'empty.flac') summary = file.summary assert summary.parse_ok is True assert summary.cuesheet is False assert summary.album_tags is False assert summary.track_tags is False assert summary.pictures is False
def test_tagged(self, datadir): """Test with a tagged FLAC file.""" file = FileInfo(datadir / 'invalid.flac') summary = file.summary assert summary.parse_ok is False assert summary.cuesheet is False assert summary.album_tags is False assert summary.track_tags is False assert summary.pictures is False
def test_decode_tracks(self, datadir): """Test the prepare_tracks function.""" info = FileInfo(datadir / 'tagged.flac') tempdir = fc.decode_tracks(info) assert tempdir is not None path = Path(tempdir.name) files = list(path.iterdir()) assert len(files) == 2 for file in files: assert TRACK_WAV_RE.match(file.name) is not None
def test_encode_tracks(self, datadir): """Test the encode_tracks function.""" info = FileInfo(datadir / 'tagged.flac') tracks = fc.prepare_tracks(info, str(datadir), 'ogg') tempdir = fc.decode_tracks(info) fc.encode_tracks(tracks, tempdir, 'ogg') path = Path(tracks[0].path).parent files = list(path.iterdir()) assert len(files) == 2 for file in files: assert file.name.endswith('.ogg')
def test_tagged(self, datadir): """Test reading a tagged FLAC file.""" file = FileInfo(datadir / 'tagged.flac') tags = file.tags album = tags.album_tags() assert 'FOO' not in album self.assert_album(album, 'Test Album', 'Test Artist', 'Test', '2018') self.assert_track(tags.track_tags(1), 'Track 1', None, None) self.assert_track(tags.track_tags(2), 'Track 2', None, None) self.assert_track(tags.track_tags(3), 'Track 3', 'true', 'Terrible')
def test_cuesheet(self, datadir): """Test with a valid FLAC file.""" file = FileInfo(datadir / 'test.flac') cuesheet = file.cuesheet assert cuesheet is not None assert cuesheet.is_cd is True assert cuesheet.lead_in == 88200 track_numbers = [t.number for t in cuesheet.tracks] assert track_numbers == [1, 2, 3, 170] audio_track_numbers = [t.number for t in cuesheet.audio_tracks] assert audio_track_numbers == [1, 2, 3]
def cover(flac): """Add cover images to tagged FLAC files.""" mb = MusicBrainz() for path in flac: info = FileInfo(path) if not info.parse_ok: continue album_tags = info.tags.album_tags() mbid = album_tags.get('RELEASE_MBID') if mbid is None: continue front = info.get_picture(FRONT_COVER_TYPE) if front is not None: continue click.echo('{} {}'.format(info.summary, path)) try: release = mb.release_by_id(mbid) data = mb.front_cover(release) if data is not None: front = fc.parse_picture(data, FRONT_COVER_TYPE) if info.set_picture(front): info.update() width = front.width height = front.height type_ = fc.picture_ext(front).upper() click.echo('- {} x {} {}'.format(width, height, type_)) else: click.echo('- No image found') except MusicBrainzError: click.echo('- Error while querying MusicBrainz') continue except Exception as e: click.echo('- Error while processing image ({})'.format(e)) continue
def test_remove_picture(self, datadir): """Test removing a picture.""" file = FileInfo(datadir / 'tagged.flac') assert file.remove_picture(3) is True assert file.remove_picture(3) is False file.update() assert file.get_picture(3) is None
def test_update_tagged(self, datadir): """Test updating a tagged FLAC file.""" file = FileInfo(datadir / 'tagged.flac') tags = file.tags album = tags.album_tags() album['ALBUM'] = 'Album' album['ARTIST'] = 'Artist' assert tags.update_album(album) is True track = tags.track_tags(1) track['TITLE'] = 'Track One' assert tags.update_track(1, track) is True track = tags.track_tags(2) assert tags.update_track(2, track) is False track = tags.track_tags(3) del track['TITLE'] assert tags.update_track(3, track) is True file.update() file = FileInfo(datadir / 'tagged.flac') tags = file.tags self.assert_album(tags.album_tags(), 'Album', 'Artist', 'Test', '2018') self.assert_track(tags.track_tags(1), 'Track One', None, None) self.assert_track(tags.track_tags(2), 'Track 2', None, None) self.assert_track(tags.track_tags(3), None, 'true', 'Terrible')
def analyze(flac, verbose, hidden): """Analyze FLAC files. For each file, shows a list of flags followed by the filename. \b Flags: - O: The file parsed successfully. - C: A cue sheet is present. - A: Album-level tags are present (any number). - T: Track-level tags are present (any number). - P: Pictures are present (any number). """ for path in flac: info = FileInfo(path) if info.parse_ok: album_tags = info.tags.album_tags() else: album_tags = {} if album_tags.get('HIDE') != 'true' and hidden: continue if not verbose: click.echo('{} {}'.format(info.summary, path)) else: img = '| |' url = '' if info.parse_ok: front = info.get_picture(FRONT_COVER_TYPE) if front is not None: width = front.width height = front.height type_ = fc.picture_ext(front).upper() img = '| {:4d} x {:4d} {} |'.format(width, height, type_) mbid = album_tags.get('RELEASE_MBID') if mbid is not None: url = ' | ' + RELEASE_URL.format(mbid) click.echo('{} {} {}{}'.format(info.summary, img, path, url))
def test_prepare_tracks_date_original(self, datadir): """Test the prepare_tracks function with a DATE_ORIGINAL tag.""" info = FileInfo(datadir / 'date_original.flac') tracks = fc.prepare_tracks(info, str(datadir), 'ogg') for track in tracks: assert track.tags['DATE'] == '1970'
def test_read_picture(self, datadir): """Test reading a picture.""" file = FileInfo(datadir / 'tagged.flac') with open(str(datadir / 'cover.png'), 'rb') as cover: data = cover.read() self.assert_picture(file, 3, 'image/png', 128, 128, 24, data)