def delete(slug): try: song = Song.get(slug) except DoesNotExist: abort(404) song.delete() return redirect(url_for('main.index'))
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)
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, )
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")
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()
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), })
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'
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
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'
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'))
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))