From a5be0e79625933cef096811064b8a5d02510d5af Mon Sep 17 00:00:00 2001 From: dexterlb Date: Thu, 23 May 2024 23:13:43 +0300 Subject: [PATCH 01/14] allow downloading songs in the UG format --- freetar/backend.py | 18 ++++++++++++++++-- freetar/templates/tab.html | 3 ++- freetar/ug.py | 9 +++++++-- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/freetar/backend.py b/freetar/backend.py index 4cfdce5..9b3a64b 100644 --- a/freetar/backend.py +++ b/freetar/backend.py @@ -1,8 +1,9 @@ -from flask import Flask, render_template, request +from flask import Flask, render_template, request, send_file from flask_minify import Minify from freetar.ug import ug_search, ug_tab import waitress +import io app = Flask(__name__) Minify(app=app, html=True, js=True, cssless=True) @@ -43,6 +44,15 @@ def show_tab2(tabid: int): tab=tab, title=f"{tab.artist_name} - {tab.song_name}") +@app.route("/download//") +def download_tab(artist: str, song: str): + tab = ug_tab(f"{artist}/{song}") + return respond_with_download(tab.raw_tab, f"{tab.artist_name} - {tab.song_name}.txt") + +@app.route("/download/") +def download_tab2(tabid: int): + tab = ug_tab(tabid) + return respond_with_download(tab.raw_tab, f"{tab.artist_name} - {tab.song_name}.txt") @app.route("/favs") def show_favs(): @@ -50,10 +60,14 @@ def show_favs(): title="Freetar - Favorites", favs=True) +def respond_with_download(content, filename): + data = io.BytesIO(content.encode('utf-8')) + return send_file(data, as_attachment=True, download_name=filename) + def main(): host = "0.0.0.0" - port = 22000 + port = 22001 if __name__ == '__main__': app.run(debug=True, host=host, diff --git a/freetar/templates/tab.html b/freetar/templates/tab.html index de389bf..76d7e8a 100644 --- a/freetar/templates/tab.html +++ b/freetar/templates/tab.html @@ -9,7 +9,8 @@ {% if tab.tuning %} Tuning: {{ tab.tuning }}
{% endif %} - View on Ultimate Guitar + View on Ultimate Guitar
+ Download (UG format)
{{ tab.artist_name }} - {{ tab.song_name }} (ver {{tab.version }})
Difficulty: {{ tab.difficulty }}
diff --git a/freetar/ug.py b/freetar/ug.py index d25c070..fe3d811 100644 --- a/freetar/ug.py +++ b/freetar/ug.py @@ -45,13 +45,14 @@ class SongDetail(): capo: str tuning: str tab_url: str + tab_url_path: str versions: list[SearchResult] = field(default_factory=list) def __init__(self, data): if __name__ == '__main__': with open("test.json", "w") as f: json.dump(data, f) - self.tab = data["store"]["page"]["data"]["tab_view"]["wiki_tab"]["content"] + self.raw_tab = data["store"]["page"]["data"]["tab_view"]["wiki_tab"]["content"] self.artist_name = data["store"]["page"]["data"]["tab"]['artist_name'] self.song_name = data["store"]["page"]["data"]["tab"]["song_name"] self.version = int(data["store"]["page"]["data"]["tab"]["version"]) @@ -66,6 +67,7 @@ def __init__(self, data): _tuning = data["store"]["page"]["data"]["tab_view"]["meta"].get("tuning") self.tuning = f"{_tuning['value']} ({_tuning['name']})" if _tuning else None self.tab_url = data["store"]["page"]["data"]["tab"]["tab_url"] + self.tab_url_path = urlparse(self.tab_url).path self.versions = [] for version in data["store"]["page"]["data"]["tab_view"]["versions"]: self.versions.append(SearchResult(version)) @@ -75,7 +77,7 @@ def __repr__(self): return f"{self.artist_name} - {self.song_name}" def fix_tab(self): - tab = self.tab + tab = self.raw_tab tab = tab.replace("\r\n", "
") tab = tab.replace("\n", "
") tab = tab.replace(" ", " ") @@ -99,6 +101,9 @@ def parse_chord(self, chord): bass = '/%s' % chord.group('bass')[1:] return '%s' % (root + quality + bass) + def download_url_ug(self): + return '/download/' + self.tab_url_path.split('/', 2)[2] + def ug_search(value: str): resp = requests.get(f"https://www.ultimate-guitar.com/search.php?search_type=title&value={quote(value)}") From 5c70830cdc91f7ceeeb38f3241b2be0d5bc029ad Mon Sep 17 00:00:00 2001 From: dexterlb Date: Thu, 23 May 2024 23:26:30 +0300 Subject: [PATCH 02/14] handle newlines earlier --- freetar/ug.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freetar/ug.py b/freetar/ug.py index fe3d811..d3db6db 100644 --- a/freetar/ug.py +++ b/freetar/ug.py @@ -52,7 +52,7 @@ def __init__(self, data): if __name__ == '__main__': with open("test.json", "w") as f: json.dump(data, f) - self.raw_tab = data["store"]["page"]["data"]["tab_view"]["wiki_tab"]["content"] + self.raw_tab = data["store"]["page"]["data"]["tab_view"]["wiki_tab"]["content"].replace("\r\n", "\n") self.artist_name = data["store"]["page"]["data"]["tab"]['artist_name'] self.song_name = data["store"]["page"]["data"]["tab"]["song_name"] self.version = int(data["store"]["page"]["data"]["tab"]["version"]) @@ -78,7 +78,6 @@ def __repr__(self): def fix_tab(self): tab = self.raw_tab - tab = tab.replace("\r\n", "
") tab = tab.replace("\n", "
") tab = tab.replace(" ", " ") tab = tab.replace("[tab]", "") From a36f477ade1baccab4bdc584a549d2a2907f386a Mon Sep 17 00:00:00 2001 From: dexterlb Date: Mon, 27 May 2024 11:32:18 +0300 Subject: [PATCH 03/14] pass format to download request and allow plain-text downloads --- freetar/backend.py | 20 ++++++++++++++++---- freetar/templates/tab.html | 13 +++++++------ freetar/ug.py | 9 ++++++++- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/freetar/backend.py b/freetar/backend.py index 9b3a64b..9d884e2 100644 --- a/freetar/backend.py +++ b/freetar/backend.py @@ -1,7 +1,7 @@ from flask import Flask, render_template, request, send_file from flask_minify import Minify -from freetar.ug import ug_search, ug_tab +from freetar.ug import ug_search, ug_tab, SongDetail import waitress import io @@ -47,12 +47,14 @@ def show_tab2(tabid: int): @app.route("/download//") def download_tab(artist: str, song: str): tab = ug_tab(f"{artist}/{song}") - return respond_with_download(tab.raw_tab, f"{tab.artist_name} - {tab.song_name}.txt") + format = request.args.get('format') + return tab_to_dl_file(tab, format) @app.route("/download/") def download_tab2(tabid: int): tab = ug_tab(tabid) - return respond_with_download(tab.raw_tab, f"{tab.artist_name} - {tab.song_name}.txt") + format = request.args.get('format') + return tab_to_dl_file(tab, format) @app.route("/favs") def show_favs(): @@ -60,7 +62,17 @@ def show_favs(): title="Freetar - Favorites", favs=True) -def respond_with_download(content, filename): +def tab_to_dl_file(tab: SongDetail, format: str): + if format == 'ug_txt': + ext = 'ug.txt' + content = tab.raw_tab + elif format == 'txt': + ext = 'txt' + content = tab.plain_text() + else: + return f'no such format: {format}', 400 + + filename = f'{tab.artist_name} - {tab.song_name}.{ext}' data = io.BytesIO(content.encode('utf-8')) return send_file(data, as_attachment=True, download_name=filename) diff --git a/freetar/templates/tab.html b/freetar/templates/tab.html index 76d7e8a..13a8780 100644 --- a/freetar/templates/tab.html +++ b/freetar/templates/tab.html @@ -10,19 +10,20 @@ Tuning: {{ tab.tuning }}
{% endif %} View on Ultimate Guitar
- Download (UG format) + Download (Plain text) + Download (UG format)
{{ tab.artist_name }} - {{ tab.song_name }} (ver {{tab.version }})
Difficulty: {{ tab.difficulty }}
- Capo: + Capo: {% if tab.capo %} {{ tab.capo -}} th fret {% else %} no capo {% endif %}
- - + +
@@ -34,7 +35,7 @@
{{ tab.artist_name }}❮❮ ❯❯
- +
Transpose @@ -79,7 +80,7 @@
{{ tab.artist_name }}
- {{ tab.tab | safe }} + {{ tab.tab | safe }}
View on Ultimate Guitar diff --git a/freetar/ug.py b/freetar/ug.py index d3db6db..bf3d761 100644 --- a/freetar/ug.py +++ b/freetar/ug.py @@ -90,6 +90,13 @@ def fix_tab(self): tab = re.sub(r'\[ch\](?P[A-Ga-g](#|b)?)(?P[^[/]+)?(?P/[A-Ga-g](#|b)?)?\[\/ch\]', self.parse_chord, tab) self.tab = tab + def plain_text(self): + tab = self.raw_tab + tab = tab.replace("[tab]", "") + tab = tab.replace("[/tab]", "") + tab = re.sub(r'\[ch\]([^\[]*)\[\/ch\]', lambda match: match.group(1), tab) + return tab + def parse_chord(self, chord): root = '%s' % chord.group('root') quality = '' @@ -100,7 +107,7 @@ def parse_chord(self, chord): bass = '/%s' % chord.group('bass')[1:] return '%s' % (root + quality + bass) - def download_url_ug(self): + def download_url(self): return '/download/' + self.tab_url_path.split('/', 2)[2] From 9c450794fff053c619f70bd30554b427de299e8e Mon Sep 17 00:00:00 2001 From: dexterlb Date: Sun, 2 Jun 2024 23:12:39 +0300 Subject: [PATCH 04/14] add chordpro download option --- freetar/backend.py | 5 ++ freetar/chordpro.py | 159 +++++++++++++++++++++++++++++++++++++ freetar/templates/tab.html | 5 +- 3 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 freetar/chordpro.py diff --git a/freetar/backend.py b/freetar/backend.py index 9d884e2..4b36af0 100644 --- a/freetar/backend.py +++ b/freetar/backend.py @@ -2,6 +2,8 @@ from flask_minify import Minify from freetar.ug import ug_search, ug_tab, SongDetail +from freetar.chordpro import song_to_chordpro + import waitress import io @@ -69,6 +71,9 @@ def tab_to_dl_file(tab: SongDetail, format: str): elif format == 'txt': ext = 'txt' content = tab.plain_text() + elif format == 'chordpro': + ext = 'cho' + content = song_to_chordpro(tab) else: return f'no such format: {format}', 400 diff --git a/freetar/chordpro.py b/freetar/chordpro.py new file mode 100644 index 0000000..9243736 --- /dev/null +++ b/freetar/chordpro.py @@ -0,0 +1,159 @@ +import json +import re +from dataclasses import dataclass, field + +from freetar.ug import SongDetail + +def song_to_chordpro(song: SongDetail): + tab_lines = untokenise_tab(intersperse_chords(tokenise_tab(song.raw_tab))) + header_lines = [ + chordpro_directive('title', f'{song.artist_name} - {song.song_name}'), + chordpro_meta('artist', song.artist_name), + chordpro_meta('capo', song.capo) if song.capo else None + ] + return ''.join((line + '\n' for line in (header_lines + tab_lines) if line is not None)) + +def chordpro_meta(key: str, value: str): + return chordpro_directive('meta', key + ' ' + value) + +def chordpro_directive(name: str, argstr: str): + if argstr: + return '{' + name + ': ' + argstr + '}' + else: + return '{' + name + '}' + +@dataclass +class Chord(): + text: str + pos: int + + def __str__(self): + return '[' + self.text + ']' + +@dataclass +class Section(): + text: str + + def id(self): + text = re.sub(r'\s+$', '_', self.text.lower()) + text = re.sub(r'[^a-z_]*', '', text) + text = re.sub(r'^verse_[0-9]*$', 'verse', text) + text = re.sub(r'_*$', '', text) + return text + + def label(self): + return self.text + +@dataclass +class SectionStart(): + sec: Section + + def __str__(self): + return chordpro_directive('start_of_' + self.sec.id(), self.sec.label()) + +@dataclass +class SectionEnd(): + sec: Section + + def __str__(self): + return chordpro_directive('end_of_' + self.sec.id(), self.sec.label()) + +@dataclass +class Instrumental(): + line: list + + def __str__(self): + return chordpro_directive('c', untokenise_line(self.line)) + +def tokenise_line(line: str): + section_match = re.match(r'^\s*\[([^\[\]]*)\]\s*$', line) + if section_match: + return SectionStart(Section(section_match.group(1))) + return list(tokenise_symbols(line)) + +def tokenise_symbols(line: str): + pos = 0 + while len(line) > 0: + chord_match = re.match(r'^\[ch\]([^[]*)\[\/ch\](.*)$', line) + if chord_match: + line = chord_match.group(2) + yield Chord(text=chord_match.group(1), pos=pos) + pos += len(chord_match.group(1)) + else: + c, line = line[0], line[1:] + pos += 1 + yield c.replace('[', '(').replace(']', ')') + + +def insert_chords_between_tokens(chords: list, line: list): + for i, x in enumerate(line): + while chords and chords[0].pos <= i: + yield chords[0] + chords = chords[1:] + yield x + + yield from chords + +def only_whitespace(line): + return type(line) is list and all((type(x) is str and x.isspace() for x in line)) + +def only_chords(line): + return type(line) is list and all((type(x) is Chord or (type(x) is str and x.isspace()) for x in line)) + +def has_chords(line): + return type(line) is list and any((type(x) is Chord for x in line)) + +def intersperse_chords(tlines): + skip = True + for this, next in zip([None] + tlines, tlines): + if skip: + skip = False + continue + if has_chords(this) and not only_chords(this): + yield Instrumental(this) + elif has_chords(this) and only_chords(this) and (not has_chords(next)): + yield list(insert_chords_between_tokens([x for x in this if type(x) is Chord], next)) + skip = True + else: + yield this + +def untokenise_line(line): + if type(line) is list: + return ''.join((str(x) for x in line)) + return str(line) + +def insert_section_ends(tlines): + cur_sec = None + for line in tlines: + if type(line) is SectionStart: + if cur_sec: + yield SectionEnd(cur_sec) + cur_sec = line.sec + yield line + + if cur_sec: + yield SectionEnd(cur_sec) + +def move_section_borders(tlines): + i = 0 + while i < len(tlines) - 1: + i = 0 + while i < len(tlines) - 1: + if only_whitespace(tlines[i]) and type(tlines[i + 1]) is SectionEnd: + tlines[i], tlines[i + 1] = tlines[i + 1], tlines[i] + break + if only_whitespace(tlines[i + 1]) and type(tlines[i]) is SectionStart: + tlines[i], tlines[i + 1] = tlines[i + 1], tlines[i] + break + i += 1 + return tlines + + +def untokenise_tab(tlines): + tlines = move_section_borders(list(insert_section_ends(tlines))) + return [untokenise_line(line) for line in tlines] + +def tokenise_tab(tab): + tab = tab.replace("[tab]", "") + tab = tab.replace("[/tab]", "") + return [tokenise_line(line) for line in tab.split('\n')] diff --git a/freetar/templates/tab.html b/freetar/templates/tab.html index 13a8780..7e2029f 100644 --- a/freetar/templates/tab.html +++ b/freetar/templates/tab.html @@ -10,8 +10,9 @@ Tuning: {{ tab.tuning }}
{% endif %} View on Ultimate Guitar
- Download (Plain text) - Download (UG format) + Download (Plain text)
+ Download (UG format)
+ Download (ChordPro)
{{ tab.artist_name }} - {{ tab.song_name }} (ver {{tab.version }})
Difficulty: {{ tab.difficulty }}
From 43f2ec70c7f45dbccfb98483901383612e2f56f5 Mon Sep 17 00:00:00 2001 From: dexterlb Date: Sun, 2 Jun 2024 23:23:10 +0300 Subject: [PATCH 05/14] implement neat download button --- freetar/static/custom.js | 8 ++++++++ freetar/templates/tab.html | 11 +++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/freetar/static/custom.js b/freetar/static/custom.js index cb7f25b..1d65e75 100644 --- a/freetar/static/custom.js +++ b/freetar/static/custom.js @@ -97,6 +97,14 @@ $('#checkbox_view_chords').click(function(){ } }); +$('#download').click(function(){ + $("#download-options").show(); +}); + +$('#download-options').click(function(){ + $("#download-options").hide(); +}); + $('#dark_mode').click(function(){ if (document.documentElement.getAttribute('data-bs-theme') == 'dark') { document.documentElement.setAttribute('data-bs-theme', 'light'); diff --git a/freetar/templates/tab.html b/freetar/templates/tab.html index 7e2029f..50f573d 100644 --- a/freetar/templates/tab.html +++ b/freetar/templates/tab.html @@ -5,14 +5,17 @@
-
+ + 📥
+ {% if tab.tuning %} Tuning: {{ tab.tuning }}
{% endif %} View on Ultimate Guitar
- Download (Plain text)
- Download (UG format)
- Download (ChordPro)
{{ tab.artist_name }} - {{ tab.song_name }} (ver {{tab.version }})
Difficulty: {{ tab.difficulty }}
From 6e4a211e176753a335106f7cbe43a9aa45310d93 Mon Sep 17 00:00:00 2001 From: dexterlb Date: Sun, 2 Jun 2024 23:28:46 +0300 Subject: [PATCH 06/14] add some more metadata to chordpro files --- freetar/chordpro.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/freetar/chordpro.py b/freetar/chordpro.py index 9243736..1a4307f 100644 --- a/freetar/chordpro.py +++ b/freetar/chordpro.py @@ -9,11 +9,18 @@ def song_to_chordpro(song: SongDetail): header_lines = [ chordpro_directive('title', f'{song.artist_name} - {song.song_name}'), chordpro_meta('artist', song.artist_name), - chordpro_meta('capo', song.capo) if song.capo else None + chordpro_meta('capo', song.capo), + chordpro_meta('tuning', song.tuning), + chordpro_meta('version', song.version), + chordpro_meta('difficulty', song.difficulty), ] - return ''.join((line + '\n' for line in (header_lines + tab_lines) if line is not None)) + return ''.join((line + '\n' for line in (header_lines + tab_lines + ['']) if line is not None)) def chordpro_meta(key: str, value: str): + if value is None: + return None + if type(value) is not str: + value = str(value) return chordpro_directive('meta', key + ' ' + value) def chordpro_directive(name: str, argstr: str): From 01d0c00d0fd0ae773a2468040c131897258108ef Mon Sep 17 00:00:00 2001 From: dexterlb Date: Mon, 3 Jun 2024 00:30:53 +0300 Subject: [PATCH 07/14] show song key --- freetar/chordpro.py | 1 + freetar/templates/tab.html | 4 ++++ freetar/ug.py | 2 ++ 3 files changed, 7 insertions(+) diff --git a/freetar/chordpro.py b/freetar/chordpro.py index 1a4307f..09c8f59 100644 --- a/freetar/chordpro.py +++ b/freetar/chordpro.py @@ -10,6 +10,7 @@ def song_to_chordpro(song: SongDetail): chordpro_directive('title', f'{song.artist_name} - {song.song_name}'), chordpro_meta('artist', song.artist_name), chordpro_meta('capo', song.capo), + chordpro_meta('key', song.key), chordpro_meta('tuning', song.tuning), chordpro_meta('version', song.version), chordpro_meta('difficulty', song.difficulty), diff --git a/freetar/templates/tab.html b/freetar/templates/tab.html index 50f573d..b42d219 100644 --- a/freetar/templates/tab.html +++ b/freetar/templates/tab.html @@ -25,6 +25,10 @@
{{ tab.artist_name }} + Key: {{ tab.key }} + {% endif %}
diff --git a/freetar/ug.py b/freetar/ug.py index bf3d761..660de9e 100644 --- a/freetar/ug.py +++ b/freetar/ug.py @@ -43,6 +43,7 @@ class SongDetail(): version: int difficulty: str capo: str + key: str tuning: str tab_url: str tab_url_path: str @@ -54,6 +55,7 @@ def __init__(self, data): json.dump(data, f) self.raw_tab = data["store"]["page"]["data"]["tab_view"]["wiki_tab"]["content"].replace("\r\n", "\n") self.artist_name = data["store"]["page"]["data"]["tab"]['artist_name'] + self.key = data["store"]["page"]["data"]["tab"].get('tonality_name') self.song_name = data["store"]["page"]["data"]["tab"]["song_name"] self.version = int(data["store"]["page"]["data"]["tab"]["version"]) self._type = data["store"]["page"]["data"]["tab"]["type"] From 6fdb8a071bab16a3b4b36d8c0f0ad5ced69581e8 Mon Sep 17 00:00:00 2001 From: dexterlb Date: Mon, 3 Jun 2024 09:10:05 +0300 Subject: [PATCH 08/14] revert change to debug port in main --- freetar/backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freetar/backend.py b/freetar/backend.py index 4b36af0..fd3adf9 100644 --- a/freetar/backend.py +++ b/freetar/backend.py @@ -84,7 +84,7 @@ def tab_to_dl_file(tab: SongDetail, format: str): def main(): host = "0.0.0.0" - port = 22001 + port = 22000 if __name__ == '__main__': app.run(debug=True, host=host, From 7f0c1f888fa3d4c27be7f68d4c37fdfd54bf1bdc Mon Sep 17 00:00:00 2001 From: dexterlb Date: Sat, 8 Jun 2024 22:30:37 +0300 Subject: [PATCH 09/14] properly detect instrumentals --- freetar/chordpro.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/freetar/chordpro.py b/freetar/chordpro.py index 09c8f59..3325b1f 100644 --- a/freetar/chordpro.py +++ b/freetar/chordpro.py @@ -111,17 +111,20 @@ def only_chords(line): def has_chords(line): return type(line) is list and any((type(x) is Chord for x in line)) +def has_lyrics_and_nothing_else(line): + return (not has_chords(line)) and (not only_whitespace(line)) + def intersperse_chords(tlines): skip = True for this, next in zip([None] + tlines, tlines): if skip: skip = False continue - if has_chords(this) and not only_chords(this): - yield Instrumental(this) - elif has_chords(this) and only_chords(this) and (not has_chords(next)): + elif has_chords(this) and only_chords(this) and (has_lyrics_and_nothing_else(next)): yield list(insert_chords_between_tokens([x for x in this if type(x) is Chord], next)) skip = True + elif has_chords(this): + yield Instrumental(this) else: yield this From 9eb4dd2e4ac3b01b34341a35f660a2e33858f29b Mon Sep 17 00:00:00 2001 From: dexterlb Date: Sat, 8 Jun 2024 23:10:11 +0300 Subject: [PATCH 10/14] don't label section endss --- freetar/chordpro.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freetar/chordpro.py b/freetar/chordpro.py index 3325b1f..b60954e 100644 --- a/freetar/chordpro.py +++ b/freetar/chordpro.py @@ -24,7 +24,7 @@ def chordpro_meta(key: str, value: str): value = str(value) return chordpro_directive('meta', key + ' ' + value) -def chordpro_directive(name: str, argstr: str): +def chordpro_directive(name: str, argstr: str = None): if argstr: return '{' + name + ': ' + argstr + '}' else: @@ -64,7 +64,7 @@ class SectionEnd(): sec: Section def __str__(self): - return chordpro_directive('end_of_' + self.sec.id(), self.sec.label()) + return chordpro_directive('end_of_' + self.sec.id()) @dataclass class Instrumental(): From c6f9cc99d2cfa5e5f5e983fa73febc2aa5cefe7d Mon Sep 17 00:00:00 2001 From: dexterlb Date: Sat, 8 Jun 2024 23:13:27 +0300 Subject: [PATCH 11/14] don't eat last line of tab --- freetar/chordpro.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freetar/chordpro.py b/freetar/chordpro.py index b60954e..26794a7 100644 --- a/freetar/chordpro.py +++ b/freetar/chordpro.py @@ -112,11 +112,11 @@ def has_chords(line): return type(line) is list and any((type(x) is Chord for x in line)) def has_lyrics_and_nothing_else(line): - return (not has_chords(line)) and (not only_whitespace(line)) + return type(line) is list and (not has_chords(line)) and (not only_whitespace(line)) def intersperse_chords(tlines): skip = True - for this, next in zip([None] + tlines, tlines): + for this, next in zip([None] + tlines, tlines + [None]): if skip: skip = False continue From b5ea23d11ac9d9299df0430c9773f0fcd4e326e1 Mon Sep 17 00:00:00 2001 From: dexterlb Date: Sun, 9 Jun 2024 22:13:41 +0300 Subject: [PATCH 12/14] do not include empty tags --- freetar/chordpro.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freetar/chordpro.py b/freetar/chordpro.py index 26794a7..a65f81d 100644 --- a/freetar/chordpro.py +++ b/freetar/chordpro.py @@ -18,7 +18,7 @@ def song_to_chordpro(song: SongDetail): return ''.join((line + '\n' for line in (header_lines + tab_lines + ['']) if line is not None)) def chordpro_meta(key: str, value: str): - if value is None: + if not value: return None if type(value) is not str: value = str(value) From a821841874f2baafbb26b9dafa0e493a367425e7 Mon Sep 17 00:00:00 2001 From: dexterlb Date: Mon, 10 Jun 2024 23:30:19 +0300 Subject: [PATCH 13/14] properly format song title --- freetar/chordpro.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freetar/chordpro.py b/freetar/chordpro.py index a65f81d..4dd906e 100644 --- a/freetar/chordpro.py +++ b/freetar/chordpro.py @@ -7,8 +7,8 @@ def song_to_chordpro(song: SongDetail): tab_lines = untokenise_tab(intersperse_chords(tokenise_tab(song.raw_tab))) header_lines = [ - chordpro_directive('title', f'{song.artist_name} - {song.song_name}'), - chordpro_meta('artist', song.artist_name), + chordpro_directive('title', song.song_name), + chordpro_directive('artist', song.artist_name), chordpro_meta('capo', song.capo), chordpro_meta('key', song.key), chordpro_meta('tuning', song.tuning), From b46740f3625e8f057682157b6d6ed3a553a9817e Mon Sep 17 00:00:00 2001 From: dexterlb Date: Fri, 19 Jul 2024 16:29:55 +0300 Subject: [PATCH 14/14] don't crash when no tuning info is present --- freetar/ug.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freetar/ug.py b/freetar/ug.py index 660de9e..c7a3851 100644 --- a/freetar/ug.py +++ b/freetar/ug.py @@ -68,6 +68,9 @@ def __init__(self, data): self.capo = data["store"]["page"]["data"]["tab_view"]["meta"].get("capo") _tuning = data["store"]["page"]["data"]["tab_view"]["meta"].get("tuning") self.tuning = f"{_tuning['value']} ({_tuning['name']})" if _tuning else None + else: + self.capo = None + self.tuning = None self.tab_url = data["store"]["page"]["data"]["tab"]["tab_url"] self.tab_url_path = urlparse(self.tab_url).path self.versions = []