songs2slides

A tool that automatically finds song lyrics and creates lyric slideshows
git clone https://git.ashermorgan.net/songs2slides/
Log | Files | Refs | README

commit a581f843a445f279e44a2debb722665153e78838
parent 600f02e3c109462ae6655fe0344dcb0541c3f21a
Author: ashermorgan <59518073+ashermorgan@users.noreply.github.com>
Date:   Mon, 19 Feb 2024 21:34:42 -0800

Implement get_slide_contents function

Diffstat:
Msongs2slides/utils.py | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mtests/test_core.py | 76+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 128 insertions(+), 11 deletions(-)

diff --git a/songs2slides/utils.py b/songs2slides/utils.py @@ -1,14 +1,16 @@ from dotenv import load_dotenv +from dataclasses import dataclass import os import requests +@dataclass class SongData: """ Represents data about a song - Attributes: - ----------- + Attributes + ---------- title : str The title of the song artist : str @@ -17,17 +19,16 @@ class SongData: The song's lyrics, with double newlines separating stanzas """ - def __init__(self, title: str, artist: str, lyrics: str): - self.title = title - self.artist = artist - self.lyrics = lyrics + title: str + artist: str + lyrics: str def get_song_data(title: str, artist:str): """ Get song data from an external API - Parameters: - ----------- + Parameters + ---------- title : str The title of the song artist : str @@ -50,3 +51,49 @@ def get_song_data(title: str, artist:str): return SongData(data['title'], data['artist'], data['lyrics']) else: raise Exception() + +def get_slide_contents(lyrics: str, lines_per_slide: int = 4): + """ + Generate slide contents from song lyrics + + Parameters + ---------- + lyrics : str + The song lyrics + lines_per_slide : int + The maximum number of lines per slide (the default is 4) + + Returns + ------- + list of str + The list of slide contents + """ + + slides = [''] + line_count = 0 + + for line in lyrics.split('\n'): + line = line.strip() + + if line == '': + # Empty line represents new slide + slides += [''] + line_count = 0 + + elif line_count < lines_per_slide: + # Add line to current slide + if line_count != 0: slides[-1] += '\n' + slides[-1] += line + line_count += 1 + + else: + # Overflow to new slide + slides += [line] + line_count = 1 + + # Remove first/last slide if empty + # len(slides) is always greater than 1 or single slide is not empty + if slides[0] == '': slides = slides[1:] + if slides[-1] == '': slides = slides[:-1] + + return slides diff --git a/tests/test_core.py b/tests/test_core.py @@ -10,9 +10,9 @@ class TestUtils(unittest.TestCase): # Mock os.getenv and requests.get mocked_env.return_value = 'api://lyrics/{artist}/{title}' - mocked_get.return_value.text = b'{"lyrics":"Test1\nTest2\nTest3\nTest4","title":"Foo","artist":"Bar","image":null}' + mocked_get.return_value.text = b'{"lyrics":"A\nB\nC\nD","title":"Foo","artist":"Bar","image":null}' mocked_get.return_value.json.return_value = { - 'lyrics': 'Test1\nTest2\nTest3\nTest4', + 'lyrics': 'A\nB\nC\nD', 'title': 'Foo', 'artist': 'Bar', } @@ -25,7 +25,7 @@ class TestUtils(unittest.TestCase): mocked_get.assert_called_with('api://lyrics/bar/foo') self.assertEqual(song_data.title, 'Foo') self.assertEqual(song_data.artist, 'Bar') - self.assertEqual(song_data.lyrics, 'Test1\nTest2\nTest3\nTest4') + self.assertEqual(song_data.lyrics, 'A\nB\nC\nD') def test_get_song_data_no_url(self): with patch('songs2slides.utils.os.getenv') as mocked_env, \ @@ -58,3 +58,73 @@ class TestUtils(unittest.TestCase): # Assert request was called mocked_get.assert_called_with('api://lyrics/bar/foo') + + def test_get_slide_contents_default(self): + # Declare song data and expected slides + lyrics = 'A\nB\nC\nD\nE\nF\n\nG\nH' + expected = ['A\nB\nC\nD', 'E\nF', 'G\nH'] + + # Get slide content + result = utils.get_slide_contents(lyrics) + + self.assertEqual(result, expected) + + def test_get_slide_contents_3_lines_per_slide(self): + # Declare song data and expected slides + lyrics = 'A\nB\nC\nD\nE\nF\n\nG\nH' + expected = ['A\nB\nC', 'D\nE\nF', 'G\nH'] + + # Get slide content + result = utils.get_slide_contents(lyrics, lines_per_slide = 3) + + self.assertEqual(result, expected) + + def test_get_slide_contents_empty_string(self): + # Declare song data and expected slides + lyrics = '' + expected = [] + + # Get slide content + result = utils.get_slide_contents(lyrics, lines_per_slide = 3) + + self.assertEqual(result, expected) + + def test_get_slide_contents_one_line(self): + # Declare song data and expected slides + lyrics = 'A' + expected = ['A'] + + # Get slide content + result = utils.get_slide_contents(lyrics) + + self.assertEqual(result, expected) + + def test_get_slide_contents_one_slide(self): + # Declare song data and expected slides + lyrics = 'A\nB\nC\nD' + expected = ['A\nB\nC\nD'] + + # Get slide content + result = utils.get_slide_contents(lyrics) + + self.assertEqual(result, expected) + + def test_get_slide_contents_tripple_newlines(self): + # Declare song data and expected slides + lyrics = 'A\nB\n\n\nC\nD' + expected = ['A\nB', '', 'C\nD'] + + # Get slide content + result = utils.get_slide_contents(lyrics) + + self.assertEqual(result, expected) + + def test_get_slide_contents_extra_whitespace(self): + # Declare song data and expected slides + lyrics = ' A\n B \nC D\nE ' + expected = ['A\nB\nC D\nE'] + + # Get slide content + result = utils.get_slide_contents(lyrics) + + self.assertEqual(result, expected)