Exemple #1
0
def delete(slug):
    try:
        song = Song.get(slug)
    except DoesNotExist:
        abort(404)
    song.delete()
    return redirect(url_for('main.index'))
Exemple #2
0
def playlist(playlist_slug):
    """Add song to playlist."""
    try:
        playlist = Playlist.get(playlist_slug)
    except DoesNotExist as e:
        sys.exit(e)

    songs = [song.slug for song in Song.all() if song not in playlist]
    try:
        song_slug = subprocess.run(
            ['fzf', '--no-sort', '--exact'],
            input='\n'.join(songs),
            universal_newlines=True,
            stdout=subprocess.PIPE,
        ).stdout.strip()
    except FileNotFoundError as e:
        sys.exit(e)

    if not song_slug:
        return

    song = Song.get(song_slug)
    # If the user selects nothing we leave the root empty, and the
    # default root is used. If he wants to explicitly set the default
    # root in case it's changed in the future, he has to select it.
    default = song.scale[0:2].strip()
    root = click.prompt(f'Root [{default}]', default='', show_default=False)
    if root and root not in SHARPS + FLATS:
        sys.exit(f'Invalid root: {root}')

    playlist.add(song_slug, root)
Exemple #3
0
def edit(slug):
    form = SongForm(request.form)
    song = Song.get(slug)

    if request.method == 'POST' and form.validate():
        song.name = form.name.data
        song.year = form.year.data
        song.artist = form.artist.data
        song.body = form.body.data
        song.scale = form.scale.data
        song.rhythm = form.rhythm.data
        song.link = form.link.data
        song.tofile()
        return redirect(url_for('main.song', slug=song.slug))

    # Populate form with song's attributes
    form.name.data = song.name
    form.year.data = song.year
    form.artist.data = song.artist
    form.body.data = song.body
    form.scale.data = song.scale
    form.rhythm.data = song.rhythm
    form.link.data = song.link

    return render_template(
        'admin/songform.html',
        form=form,
        action=url_for('admin.edit', slug=slug),
        title=song.name,
    )
Exemple #4
0
def export():
    """Export all playlist's songs to file."""
    playlist = Playlist.get('giannis')
    with open('asdf.txt', 'w') as f:
        for song in playlist.songs:
            song = Song.get(song.slug, root=song.root)
            f.write(f"{song.name} - {song.artist} ({song.year})\n\n")
            f.write(f"{song.info()}\n\f")
Exemple #5
0
    def add(self, song_slug, root=None):
        if root and not re.match('^[A-G][bs]?$', root):
            raise InvalidNote(f"'{root}' is not a valid note")

        self.songs = [song for song in self.songs if song.slug != song_slug]
        song = Song.get(song_slug, root=root)
        self.songs.append(song)
        self.songs.sort(key=lambda song: unaccented(song.name))
        self.tofile()
Exemple #6
0
def song(slug, semitones=None, root=None):
    """A song optionally transposed by given semitones."""
    try:
        song = Song.get(slug, semitones=semitones, root=root, unicode=True)
    except DoesNotExist:
        abort(404)
    except InvalidNote as e:
        return jsonify({'message': str(e)}), 400
    return jsonify({
        'name': song.name,
        'artist': song.artist,
        'link': song.link,
        'info': song.info(html=True),
    })
Exemple #7
0
def test_prepare_song(client):
    song1 = SongFactory(name='name_a', scale='D#')
    song2 = SongFactory(name='name_b', scale='Eb')
    song1.tofile()
    song2.tofile()
    assert Song.get('name_a', unicode=True).scale == 'D♯'
    assert Song.get('name_b', unicode=True).scale == 'E♭'
    assert Song.get('name_a', semitones=-2).scale == 'C#'
    assert Song.get('name_b', semitones=-2).scale == 'Db'
    assert Song.get('name_a', root='B').scale == 'B'
    assert Song.get('name_b', root='B').scale == 'B'
Exemple #8
0
def test_save_delete(client):
    with client.session_transaction() as session:
        session['logged_in'] = True
    song = SongFactory(body='Bm F# Bm')
    song.tofile()
    assert len(Song.all()) == 1
    url = url_for('admin.save', slug='name', semitones=1)
    resp = client.get(url, follow_redirects=True)
    assert resp.status_code == 200
    song = Song.get('name')
    assert song.body == 'Cm G  Cm'
    resp = client.get(url_for('admin.delete', slug='name'),
                      follow_redirects=True)
    assert Song.all() == []
    resp = client.get(url_for('admin.delete', slug='name'),
                      follow_redirects=True)
    assert 'Δεν υπάρχει τέτοια σελίδα'.encode() in resp.data
Exemple #9
0
def test_add(client):
    with client.session_transaction() as session:
        session['logged_in'] = True
    resp = client.get(url_for('admin.add'))
    assert 'Νέο τραγούδι'.encode() in resp.data
    resp = client.post(
        url_for('admin.add'),
        data={
            'name': 'name',
            'artist': 'artist',
            'scale': 'scale',
            'rhythm': 'rhythm',
            'body': 'body',
            'link': 'https://www.youtube.com/watch?v=asdfasdf',
        },
        follow_redirects=True
    )
    song = Song.get('name')
    assert song.name == 'name'
Exemple #10
0
def check():
    """Check and fix audio files."""
    def clear_metadata(filename):
        audio = ID3(filename)
        keys = list(audio.keys())
        if keys:
            for key in keys:
                audio.delall(key)
            audio.save()

    def set_comment(filename, comment):
        audio = ID3(filename)
        audio.add(COMM(encoding=3, text=comment))
        audio.save()

    def get_comment(filename):
        audio = ID3(filename)
        comments = audio.getall("COMM")
        if not comments:
            return
        return comments[0].text[0]

    def download(song):
        """Download `song`'s YouTube video and convert to mp3."""
        filename = ''

        def download_hook(d):
            nonlocal filename
            if d['status'] == 'finished':
                filename = d['filename']
                print("Done downloading, now converting...")

        ydl_opts = {
            'format':
            'bestaudio/best',
            'postprocessors': [{
                'key': 'FFmpegExtractAudio',
                'preferredcodec': 'mp3',
                'preferredquality': '192',
            }],
            'progress_hooks': [download_hook],
        }
        with youtube_dl.YoutubeDL(ydl_opts) as ydl:
            try:
                ydl.download([song.link])
            except youtube_dl.DownloadError as e:
                print(
                    click.style(f"Couldn't download {song.name}: {e}",
                                fg='bright_red'))
                return

        path = Path(filename).with_suffix('.mp3')
        path.rename(song.audio_path)
        set_comment(song.audio_path, song.youtube_id)

    def check_link(song):
        """Return whether `song`'s YouTube link is still valid."""
        url = f'http://img.youtube.com/vi/{song.youtube_id}/mqdefault.jpg'
        try:
            response = requests.get(url)
        except requests.ConnectionError as e:
            sys.exit(e)
        if response.status_code != 200:
            return False
        return True

    directory: Path = app.config['DIR'] / 'songs'
    songs = [Song.get(path.name) for path in directory.iterdir()]
    songs.sort(key=lambda song: unaccented(song.name))
    num = len(songs)
    for i, song in enumerate(songs, start=1):
        print(f"{i}/{num}", end='\t')
        if song.youtube_id:
            if not check_link(song):
                assert song.audio_path.is_file()
                print(
                    click.style(f"{song.name} has invalid youtube id",
                                fg='bright_red'))
            elif song.audio_path.is_file():
                youtube_id = get_comment(song.audio_path)
                if song.youtube_id != youtube_id:
                    print(f"{song.name} had changed "
                          f"({song.youtube_id} != {youtube_id})")
                    if click.confirm("Replace?"):
                        song.audio_path.unlink()
                        print(f"Downloading {song.name}...")
                        download(song)
                        print()
                else:
                    print(song.name, "is OK")
            else:
                print(f"Downloading {song.name}...")
                download(song)
                print()
        else:
            assert song.audio_path.is_file()
            print(song.name, "has no youtube id but audio is downloaded")
            clear_metadata(song.audio_path)

        if not song.year:
            print(click.style(f"{song.name} has no year", fg='bright_red'))
Exemple #11
0
def save(slug, semitones=None, root=None):
    """Save a transposed song to the database."""
    song = Song.get(slug, semitones=semitones, root=root)
    song.tofile()
    return redirect(url_for('main.song', slug=song.slug))