Compare commits

..

6 Commits

Author SHA1 Message Date
Effie
90a4e3c8f3 organizes the motif index and starts work on search bar 2023-06-13 18:22:08 +10:00
Effie
50752c32fd develops the css to add song and motif boxes 2023-06-09 16:16:47 +10:00
Effie
12d51dd7d2 adds nav to the header 2023-06-07 20:02:10 +10:00
Effie
df46e34049 adds a placeholder header and footer 2023-06-06 18:46:12 +10:00
Effie
f1f6dd6717 added motif and song indexes 2023-06-06 17:53:21 +10:00
Effie
1d39f61d48 orders database queries and adds motif types 2023-06-06 14:34:34 +10:00
18 changed files with 642 additions and 62 deletions

246
app.py
View File

@ -3,10 +3,243 @@ import sqlite3
app = flask.Flask('songbook')
# BLANK PAGE
# @app.route('/')
# def page():
# db = sqlite3.connect("file:songbook.sqlite?mode=ro", uri=True)
# return flask.render_template('.html',
# )
# HOMEPAGE
def openbook():
return sqlite3.connect("file:songbook.sqlite?mode=ro", uri=True)
@app.route('/')
def homepage():
return flask.render_template('home.html')
# SEARCH RESULTS
@app.route('/search')
def searchpage():
db = openbook()
searchargs = flask.request.args['q']
searchresult = db.execute('''
select
'song', id, name
from
song
where
name
like
'%' || ? || '%'
union all
select
'album', id, name
from
album
where
name
like
'%' || ? || '%'
''',
(searchargs,searchargs)
).fetchall()
return flask.render_template('',
album_info=album_info,
song_info=song_info
)
# SONG INDEX
@app.route('/song')
def song_redirect():
return flask.redirect("/index", code=308)
@app.route('/index')
def songindex():
db = openbook()
album_info = db.execute('''
select
id, name, date
from
album
order by
id
''').fetchall()
song_info = db.execute('''
select
song_album.song_id, album_id, track, song.name, song.name_jp, count(clip.song_id)
from
song_album
join
song
on
song_album.song_id=song.id
left join
clip on clip.song_id=song.id
group by
song_album.song_id, album_id, track, song.name, song.name_jp
order by
album_id,
track
''').fetchall()
return flask.render_template('songindex.html',
album_info=album_info,
song_info=song_info
)
# MOTIF INDEX
@app.route('/motif')
def motifindex():
db = openbook()
category_info = db.execute('''
select
category.id, category.name
from
category
join
motif
on
category.id=motif.category
group by
category.id, category.name
order by
category.id
''').fetchall()
motif_info = db.execute('''
select
id, name, category, count(clip.motif_id)
from
motif
left join
clip
on
clip.motif_id=motif.id
group by
id, name, category
order by
clip.song_id
''').fetchall()
return flask.render_template('motifindex.html',
category_info = category_info,
motif_info = motif_info
)
# ALBUM PAGES
@app.route('/album/<int:id>')
def albumpage(id):
db = openbook()
album_info = db.execute('''
select
name, date
from
album
where
id = ?
''',
(id,)
).fetchone()
song_info = db.execute('''
select
song_id, track, song.name, song.name_jp
from
song_album
join
song
on
song_album.song_id=song.id
where
album_id = ?
''',
(id,)
).fetchall()
return flask.render_template('album.html',
album_info=album_info,
song_info=song_info
)
# MOTIF PAGES
@app.route('/motif/<int:id>')
def motifpage(id):
db = openbook()
# query motif
name, = db.execute('''
select
name
from
motif
where
id = ?
''',
(id,)
).fetchone()
# query clips
clip_info = db.execute('''
select
song_id, motif_id, song.name
from
clip
join
song
on
clip.song_id=song.id
where
motif_id = ?
order by
feature desc,
song_id
''',
(id,)
).fetchall()
return flask.render_template('motif.html',
name=name,
clip_info=clip_info
)
# SONG PAGES
@app.route('/song/<int:id>')
def songpage(id):
db = sqlite3.connect("file:songbook.sqlite?mode=ro", uri=True)
db = openbook()
# query song name
@ -25,7 +258,7 @@ def songpage(id):
album_info = db.execute('''
select
track, album.name, album.code
album_id, track, album.name, album.code
from
song_album
join
@ -34,6 +267,8 @@ def songpage(id):
song_album.album_id=album.id
where
song_id = ?
order by
album_id
''',
(id,)
).fetchall()
@ -55,6 +290,9 @@ def songpage(id):
song_artist.credit_id=credit.id
where
song_id = ?
order by
credit_id,
artist.name_rm
''',
(id,)
).fetchall()
@ -72,11 +310,13 @@ def songpage(id):
clip.motif_id=motif.id
where
song_id = ?
order by
start_ms
''',
(id,)
).fetchall()
return flask.render_template('song.jinja',
return flask.render_template('song.html',
name=name,
id=id,
album_info=album_info,

View File

@ -10,8 +10,8 @@ cur = db.cursor()
with open('db.sql') as file:
db.executescript(file.read())
data = pyexcel_odsr.get_data('reference.ods')
clipdata = pyexcel_odsr.get_data('reference_clips.ods')
data = pyexcel_odsr.get_data('reference.ods', skip_empty_rows=True)
clipdata = pyexcel_odsr.get_data('reference_clips.ods', skip_empty_rows=True)
# import song
cur.executemany(
@ -37,18 +37,22 @@ cur.executemany(
data['Credit'][1:]
)
print(clipdata['Motif'][1:])
# import motif
cur.executemany(
'insert into motif(id,name) values(?, ?)',
clipdata['Motif'][1:]
'insert into motif(id,name,category) values(?, ?, ?)',
((row[0],row[1],'' if len(row)<3 else row[2]) for row in clipdata['Motif'][1:])
)
# import clip
cur.executemany(
'insert into clip(start_ms,duration_ms,song_id,motif_id) values (?, ?, ?, ?)',
clipdata['Clip'][1:]
'insert into clip(start_ms,duration_ms,song_id,motif_id,feature) values (?, ?, ?, ?, ?)',
((row[0],row[1],row[2],row[3],0 if len(row)<5 else row[4]) for row in clipdata['Clip'][1:])
)
# import category
cur.executemany(
'insert into category(id,name) values (?, ?)',
clipdata['Category'][1:]
)
# import song x album

7
db.sql
View File

@ -30,6 +30,12 @@ create table credit(
);
create table motif(
id int primary key,
name text not null,
category int not null
);
create table category(
id int primary key,
name text not null
);
@ -39,6 +45,7 @@ create table clip(
motif_id int not null references motif(id),
start_ms int not null,
duration_ms int not null,
feature int not null,
primary key(song_id,motif_id)
);

Binary file not shown.

Binary file not shown.

180
static/style.css Normal file
View File

@ -0,0 +1,180 @@
/* body{
max-width:50rem;margin-left:auto;margin-right:auto;
} */
body{
margin: 0;
}
header{
background-color: lch(80% 25 79);
margin: 0;
border-style: outset;
}
.top-buttons{
display: flex;
padding-left: 0;
margin: 0;
padding: 2rem;
column-gap: 1rem;
}
.top-buttons li{
flex: auto;
border-style: outset;
list-style: none;
text-align: center;
}
.top-buttons li:nth-child(2){
flex: none;
}
main{
background-color: lch(80% 25 79);
max-width: 50rem;
margin-left: auto;
margin-right: auto;
}
main ul{
margin: 0;
}
h1{
margin: 0;
}
h2{
margin: 0;
}
a{
text-decoration: none;
}
.album-list{
display: flex;
flex-direction: column;
gap: 1rem;
}
.album-box{
gap: 1rem;
}
.album-box h2{
color: white;
border-style: solid;
border-color: lch(25% 40 45);
margin: 1rem;
margin-bottom: 0px;
position: sticky;
top: 0px;
background: lch(25% 40 45);
}
.album-box ul{
display: flex;
flex: auto;
flex-direction: column;
padding-left: 0;
}
.song-box{
list-style: none;
min-height: 1.5rem;
margin: 1rem;
margin-bottom: 0;
margin-left: 4rem;
background: lch(25% 40 45);
display: flex;
flex: auto;
flex-wrap: wrap;
justify-content: left;
column-gap: 1rem;
align-content: bottom;
white-space: nowrap;
overflow: hidden;
}
.song-box span{
color: white;
text-align: bottom;
}
.track-name-jp{
}
.clip-count{
margin-left: auto;
min-width: 2rem;
text-align: right;
}
.category-list{
display: flex;
flex-wrap: wrap;
align-content: flex-start;
flex-basis: 51%;
gap: 1rem;
}
.category-box{
gap: 1rem;
margin-left: auto;
margin-right: auto;
width: 24rem;
}
.category-box h2{
color: white;
border-style: solid;
border-color: lch(25% 40 45);
margin: 1rem;
margin-bottom: 0px;
background: lch(25% 40 45);
}
.category-box ul{
display: flex;
flex: initial;
flex-direction: column;
padding-left: 0;
}
.motif-box{
list-style: none;
min-height: 1.5rem;
margin: 1rem;
margin-bottom: 0;
background: lch(25% 40 45);
display: flex;
flex: auto;
justify-content: center;
}
.motif-box span{
color: white;
vertical-align: middle;
}
.motif-name::before{
content: "T";
margin-right: 2rem;
visibility: hidden;
}
.motif-name{
text-align: center;
margin: auto;
}
.motif-count{
position: absolute, right;
text-align: right;
width: 2rem;
}

16
templates/album.html Normal file
View File

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block title %}{{ album_info[0] }}{% endblock %}
{% block content %}
<h1>{{ album_info[0] }}</h1>
<p>Released: {{ album_info[1] }}
<!-- List songs -->
<ul>
{% for song_id, track, name, name_jp in song_info %}
<li><a href="{{ url_for('songpage', id=song_id) }}">#{{ track }}: {{name}} / {{name_jp}}</li></a>
{% endfor %}
</ul>
{% endblock %}

20
templates/base.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{% block title required %}{% endblock %} - Eorzea Songbook</title>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=1" />
<meta name="description" content="Listen to the motifs of Final Fantasy XIV's soundtrack." />
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" />
<link rel="icon" sizes="16x16 32x32 48x48" type="image/png" href="" />
<script src="https://unpkg.com/htmx.org@1.9.2" integrity="sha384-L6OqL9pRWyyFU3+/bjdSri+iIphTN/bvYyM37tICVyOJkWZLpP2vGn6VUEXgzg6h" crossorigin="anonymous"></script>
</head>
<body>
{% block nav %}{% include 'nav.html' %}{% endblock %}
<main>{% block content required %}{% endblock %}</main>
{% block footer %}{% include 'footer.html' %}{% endblock %}
</body>
</html>

View File

@ -1,14 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>{% block title %}{% endblock %} - Eorzea Songbook</title>
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>

5
templates/footer.html Normal file
View File

@ -0,0 +1,5 @@
<footer>
<p> The footer will be in here, when it's ready. </p>
</footer>

13
templates/home.html Normal file
View File

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% block title %}Home{% endblock %}
{% block nav %}{% endblock %}
{% block content %}
<h1>The Eorzea Songbook</h1>
<a href="{{ url_for('songindex') }}">View Songs</a>
<a href="{{ url_for('motifindex') }}">View Motifs</a>
{% block footer %}{% endblock %}
{% endblock %}

23
templates/motif.html Normal file
View File

@ -0,0 +1,23 @@
{% extends "base.html" %}
{% block title %}{{ name }}{% endblock %}
{% block content %}
<h1> Motif: {{ name }} </h1>
<!-- Clips -->
<p>Clips: </p>
<ul>
{% for song_id, motif_id, song in clip_info %}
<li>
<audio
controls
src = "{{ url_for('static', filename='clip/') }}{{ '%04d' % song_id }}-{{ '%03d' % motif_id }}.flac" type="audio/flac">
</audio>
<a href="/song/{{ song_id }}">{{ song }}</a>
</li>
{% endfor %}
</ul>
{% endblock %}

35
templates/motifindex.html Normal file
View File

@ -0,0 +1,35 @@
{% extends 'base.html' %}
{% block title %}Motif Index{% endblock %}
{% block content %}
<section class="category-list">
{% for category_id, name in category_info %}
<div class="category-box">
<h2>{{ name }}</h2>
<ul>
{% for motif_id, motif, category, count in motif_info if category == category_id %}
<a href="{{ url_for('motifpage', id=motif_id) }}"><li class="motif-box">
<span class="motif-name">{{ motif }}</span>
{% if count %}
<span class="motif-count">x{{ count }}</span>
{% endif %}
</li></a>
{% endfor %}
</ul>
</div>
{% endfor %}
<div class="category-box">
<h2>Misc.</h2>
<ul>
{% for motif_id, motif, category, count in motif_info if category == '' %}
<a href="{{ url_for('motifpage', id=motif_id) }}"><li class="motif-box">
<span class="motif-name">{{ motif }}</span>
<span class="motif-count">{% if count %} x{{ count }} {% endif %}</span>
</li></a>
{% endfor %}
</ul>
</div>
</section>
{% endblock %}

20
templates/nav.html Normal file
View File

@ -0,0 +1,20 @@
<header>
<nav>
<ul class="top-buttons">
<li><a href="{{ url_for('songindex') }}">Songs</a></li>
<li><a href="{{ url_for('homepage') }}">Home</a></li>
<li><a href="{{ url_for('motifindex') }}">Motifs</a></li>
</ul>
</nav>
<input
type="search"
name="q"
placeholder="Search..."
hx-get="/search"
hx-trigger="search"
hx-target="#search-results"
/>
<div id="search-results"></div>
</header>

View File

40
templates/song.html Normal file
View File

@ -0,0 +1,40 @@
{% extends "base.html" %}
{% block title %}{{ name }}{% endblock %}
{% block content %}
<h1>Now Playing: {{ id }}. {{ name }} ({% for _, _, _, album_code in album_info %}{{ album_code }}{% if not loop.last %}, {% endif %}{% endfor %})
</h1>
<!-- Artist info -->
<h2>Artist: </h2>
<ul>
{% for name, name_rm, credit in artist_info %}
<li>{{ name_rm }} ({{ name }}): {{ credit }}</li>
{% endfor %}
</ul>
<!-- Album info -->
<h2>Album: </h2>
<ul>
{% for album_id, track, album_name, album_code in album_info %}
<li><a href="{{ url_for('albumpage', id=album_id) }}">{{ album_name }}</a> #{{ track }}</li>
{% endfor %}
</ul>
<!-- Clips -->
<h2>Clips: </h2>
<ul>
{% for song_id, motif_id, motif in clip_info %}
<li>
<audio
controls
src = "{{ url_for('static', filename='clip/') }}{{ '%04d' % song_id }}-{{ '%03d' % motif_id }}.flac" type="audio/flac">
</audio>
<a href = "{{ url_for('motifpage', id=motif_id) }}"> {{ motif }} </a>
</li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -1,37 +0,0 @@
{% extends "base.jinja" %}
{% block title %}{{ name }}{% endblock %}
{% block content %}
<h1>Now Playing: {{ id }}. {{ name }} ({% for _, _, album_code in album_info %}{{ album_code }}{% if not loop.last %}, {% endif %}{% endfor %})
</h1>
<!-- Artist info -->
<p>Artist: </p>
<ul>
{% for name, name_rm, credit in artist_info %}
<li>{{ name_rm }} ({{ name }}): {{ credit }}</li>
{% endfor %}
</ul>
<!-- Album info -->
<p>Album: </p>
<ul>
{% for track, album_name, album_code in album_info %}
<li>{{ album_name }} #{{ track }}</li>
{% endfor %}
</ul>
<p>Clips: </p>
<ul>
{% for song_id, motif_id, motif in clip_info %}
<li>
<audio
controls
src = "/static/clip/{{ '%04d' % song_id }}-{{ '%03d' % motif_id }}.flac">
</audio> {{ motif }}
</li>
{% endfor %}
{% endblock %}

28
templates/songindex.html Normal file
View File

@ -0,0 +1,28 @@
{% extends 'base.html' %}
{% block title %}Song Index{% endblock %}
{% block content %}
<section class="album-list">
{% for album_id, name, date in album_info %}
<div class="album-box">
<h2>{{ name }}</h2>
<ul>
{% for song_id, album, track, name, name_jp, count in song_info if album_id == album %}
<a href="{{ url_for('songpage', id=song_id) }}"><li class="song-box">
<span class="track-number">#{{ track }}: </span>
<span class="track-name">{{ name }}</span>
{% if name_jp %}
<span class="track-name-jp">{{ name_jp }}</span>
{% endif %}
<span class="clip-count">{% if count > 0 %} x{{ count }} {% endif %}</span>
</li></a>
{% endfor %}
</ul>
</div>
{% endfor %}
</section>
{% endblock %}