@@ -1,6 +1,6 @@ | |||||
# Labelize | # Labelize | ||||
Music album web page generator : 1 folder with music files + 1 cover image = 1 single page app with audio player | |||||
Music band web page generator : 1 folder with music files + images + 1 optional configuration file = 1 single page app with audio player | |||||
# Installation | # Installation | ||||
@@ -11,10 +11,28 @@ $ pip install -r requirements.txt | |||||
``` | ``` | ||||
#Configuration | |||||
In a labelize.yaml file : | |||||
```` | |||||
band: band name | |||||
images: | |||||
cover: my_cover_image.png | |||||
contact: my_contact_image.png | |||||
albums_section : | |||||
title: Album section tile | |||||
albums: | |||||
- year : 2019 | |||||
title : my album title | |||||
- year : 2018 | |||||
title : my album title | |||||
```` | |||||
# Usage | # Usage | ||||
``` | ``` | ||||
$ python labelize.py inputDirectory outputDirectory | |||||
$ python labelize.py [inputDirectory] [outputDirectory] | |||||
``` | ``` | ||||
# Credentials | # Credentials | ||||
@@ -1,60 +1,100 @@ | |||||
@font-face { | |||||
font-family: "work-sans"; | |||||
src: url('../fonts/work-sans/WorkSans-Regular.otf'); | |||||
} | |||||
:root{ | |||||
--footer-font-family : 'work-sans'; | |||||
--pretty-margin: 0.5rem; | |||||
--main-color: red; | |||||
--secondary-color: rgb(0,42,255); | |||||
} | |||||
*{ | *{ | ||||
margin: 0; | margin: 0; | ||||
padding: 0; | padding: 0; | ||||
} | } | ||||
body{ | |||||
font-family: var(--footer-font-family); | |||||
font-size: 2rem; | |||||
letter-spacing: 0.1rem; | |||||
} | |||||
.point { | |||||
margin: 0px var(--pretty-margin); | |||||
} | |||||
.pipe { | |||||
margin: 0px calc(3 * var(--pretty-margin)); | |||||
} | |||||
section{ | section{ | ||||
height: 100vh; | height: 100vh; | ||||
} | } | ||||
section h2 { | |||||
font-size: 2rem; | |||||
} | |||||
section:nth-of-type(odd){ | section:nth-of-type(odd){ | ||||
background-color: white; | background-color: white; | ||||
color: blue; | |||||
color: var(--secondary-color); | |||||
} | } | ||||
section:nth-of-type(even){ | section:nth-of-type(even){ | ||||
background-color: blue; | |||||
background-color: var(--secondary-color); | |||||
color: white; | color: white; | ||||
} | } | ||||
.cover{ | |||||
section.cover{ | |||||
background : url(../images/cover.png) no-repeat center center; | background : url(../images/cover.png) no-repeat center center; | ||||
background-color: var(--main-color); | |||||
} | } | ||||
.one-dpi{ | |||||
position : fixed; | |||||
bottom: 0; | |||||
right: 0; | |||||
width : 72px; | |||||
height: 72px; | |||||
section.albums{ | |||||
background-color: black; | background-color: black; | ||||
z-index: 10; | |||||
cursor: pointer; | |||||
color:white; | |||||
display: flex; | |||||
flex-direction: column; | |||||
justify-content: center; | |||||
line-height: 200%; | |||||
} | } | ||||
.one-dpi.clicked { | |||||
background : white; | |||||
section.albums > * { | |||||
padding-left: 10vw; | |||||
} | } | ||||
.player { | |||||
position : fixed; | |||||
right : 0; | |||||
top : 0; | |||||
height : 100vh; | |||||
width : 25vw; | |||||
color : black; | |||||
z-index: 5; | |||||
background-color: black; | |||||
color: white; | |||||
padding: 25px; | |||||
section.albums h2 { | |||||
padding-left: 20vw; | |||||
font-weight: normal; | |||||
} | } | ||||
.player ul { | |||||
section.albums ul { | |||||
list-style-type: none; | list-style-type: none; | ||||
} | } | ||||
.player li { | |||||
height: 10vh; | |||||
section.contact{ | |||||
display : flex; | display : flex; | ||||
flex-direction: column; | |||||
justify-content: center; | |||||
align-items: center; | align-items: center; | ||||
position :relative; | |||||
background : url(../images/contact.png) no-repeat center center; | |||||
background-color: var(--secondary-color); | |||||
color:white; | |||||
} | |||||
footer{ | |||||
font-size: 0.92rem; | |||||
position: absolute; | |||||
width: 100%; | |||||
bottom: 0; | |||||
text-align: center; | |||||
} | } | ||||
.hidden{ | .hidden{ | ||||
@@ -30,6 +30,7 @@ | |||||
height:var(--one-dpi); | height:var(--one-dpi); | ||||
background-color: white; | background-color: white; | ||||
cursor: pointer; | cursor: pointer; | ||||
z-index:10; | |||||
} | } | ||||
.dd-player-switch.opened{ | .dd-player-switch.opened{ | ||||
@@ -47,6 +48,7 @@ | |||||
margin-left : calc(var(--one-dpi) / 2); | margin-left : calc(var(--one-dpi) / 2); | ||||
color: var(--dd-blue); | color: var(--dd-blue); | ||||
font-size: 1.5rem; | font-size: 1.5rem; | ||||
list-style-type : none; | |||||
} | } | ||||
.dd-player ul li img{ | .dd-player ul li img{ |
@@ -1,4 +1,5 @@ | |||||
Copyright 2012 Google Inc. All Rights Reserved. | |||||
Copyright (c) 2014-2015 Wei Huang (wweeiihhuuaanngg@gmail.com) | |||||
This Font Software is licensed under the SIL Open Font License, Version 1.1. | This Font Software is licensed under the SIL Open Font License, Version 1.1. | ||||
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL |
@@ -1,7 +1,12 @@ | |||||
var current = undefined | |||||
const progressMargin = 50; | |||||
var progress = undefined; | |||||
import DedePlayer from './player.js'; | |||||
window.addEventListener('DOMContentLoaded', (event) => { | window.addEventListener('DOMContentLoaded', (event) => { | ||||
const player = new DedePlayer(document.getElementById('player')); | |||||
}) | |||||
//var current = undefined | |||||
//const progressMargin = 50; | |||||
//var progress = undefined; | |||||
//window.addEventListener('DOMContentLoaded', (event) => { | |||||
// progress = document.getElementById('player-progress'); | // progress = document.getElementById('player-progress'); | ||||
// document.getElementById('player-progress').addEventListener('click', (e) => { | // document.getElementById('player-progress').addEventListener('click', (e) => { | ||||
// if(current){ | // if(current){ | ||||
@@ -16,50 +21,50 @@ window.addEventListener('DOMContentLoaded', (event) => { | |||||
// toggler.addEventListener('click', (e) => { | // toggler.addEventListener('click', (e) => { | ||||
// document.getElementsByTagName('ul')[0].classList.toggle('invisible'); | // document.getElementsByTagName('ul')[0].classList.toggle('invisible'); | ||||
// }) | // }) | ||||
}); | |||||
function togglePlay(src) { | |||||
let media = src.getElementsByTagName('audio')[0]; | |||||
let icon = src.getElementsByTagName('img')[0]; | |||||
if (current){//stops current media and reset its play icon | |||||
pause(); | |||||
} | |||||
if(current != src){ | |||||
//sets current media icon and plays media | |||||
icon.setAttribute('src','assets/pause.png'); | |||||
media.play(); | |||||
progress.max = Math.floor(media.duration); | |||||
current = src; | |||||
document.getElementById('time-info').style.visibility = 'visible'; | |||||
} | |||||
else{ | |||||
current = undefined; | |||||
} | |||||
} | |||||
function pause(){ | |||||
current.getElementsByTagName('audio')[0].pause(); | |||||
current.getElementsByTagName('img')[0].setAttribute('src','assets/play.png'); | |||||
document.getElementById('time-info').style.visibility = 'hidden'; | |||||
} | |||||
function updateProgress(media){ | |||||
progress.value = Math.floor(media.currentTime); | |||||
document.getElementById('time-info').innerHTML = | |||||
prettyDuration(media.currentTime) + ' / ' + prettyDuration(media.duration); | |||||
} | |||||
function prettyDuration(duration) { | |||||
let sec = Math.floor( duration ); | |||||
let min = Math.floor( sec / 60 ); | |||||
min = min >= 10 ? min : '0' + min; | |||||
sec = Math.floor( sec % 60 ); | |||||
sec = sec >= 10 ? sec : '0' + sec; | |||||
return min + ':' + sec; | |||||
} | |||||
function togglePlayer(playerId, dpiId){ | |||||
document.getElementById(playerId).classList.toggle('hidden'); | |||||
document.getElementById(dpiId).classList.toggle('clicked'); | |||||
} | |||||
//}); | |||||
// | |||||
// | |||||
//function togglePlay(src) { | |||||
// let media = src.getElementsByTagName('audio')[0]; | |||||
// let icon = src.getElementsByTagName('img')[0]; | |||||
// if (current){//stops current media and reset its play icon | |||||
// pause(); | |||||
// } | |||||
// if(current != src){ | |||||
// //sets current media icon and plays media | |||||
// icon.setAttribute('src','assets/pause.png'); | |||||
// media.play(); | |||||
// progress.max = Math.floor(media.duration); | |||||
// current = src; | |||||
// document.getElementById('time-info').style.visibility = 'visible'; | |||||
// } | |||||
// else{ | |||||
// current = undefined; | |||||
// } | |||||
//} | |||||
// | |||||
//function pause(){ | |||||
// current.getElementsByTagName('audio')[0].pause(); | |||||
// current.getElementsByTagName('img')[0].setAttribute('src','assets/play.png'); | |||||
// document.getElementById('time-info').style.visibility = 'hidden'; | |||||
//} | |||||
// | |||||
//function updateProgress(media){ | |||||
// progress.value = Math.floor(media.currentTime); | |||||
// document.getElementById('time-info').innerHTML = | |||||
// prettyDuration(media.currentTime) + ' / ' + prettyDuration(media.duration); | |||||
//} | |||||
// | |||||
//function prettyDuration(duration) { | |||||
// let sec = Math.floor( duration ); | |||||
// let min = Math.floor( sec / 60 ); | |||||
// min = min >= 10 ? min : '0' + min; | |||||
// sec = Math.floor( sec % 60 ); | |||||
// sec = sec >= 10 ? sec : '0' + sec; | |||||
// return min + ':' + sec; | |||||
//} | |||||
// | |||||
//function togglePlayer(playerId, dpiId){ | |||||
// document.getElementById(playerId).classList.toggle('hidden'); | |||||
// document.getElementById(dpiId).classList.toggle('clicked'); | |||||
//} |
@@ -0,0 +1,173 @@ | |||||
export default class DedePlayer{ | |||||
constructor(container){ | |||||
this.currentAudio = null; | |||||
this.playing = false; | |||||
this.playerContainer = document.createElement("div"); | |||||
this.container = container; | |||||
this.playerContainer.classList.add('dd-player'); | |||||
this.switcher = document.createElement("div"); | |||||
this.switcher.classList.add('dd-player-switch'); | |||||
this.playerContainer.classList.add('collapsed'); | |||||
//document.body.appendChild(this.container); | |||||
document.body.appendChild(this.switcher); | |||||
this.switcher.addEventListener('click', event => { | |||||
this.playerContainer.classList.toggle('collapsed'); | |||||
this.switcher.classList.toggle('opened'); | |||||
}); | |||||
let title = document.createElement("h3"); | |||||
//title.innerHTML = playList.title; | |||||
title.appendChild(document.createTextNode(this.container.dataset.title)); | |||||
this.addProgressBar(); | |||||
// this.container.appendChild(title); | |||||
this.bindAudioTags(); | |||||
this.addPlayButton(); | |||||
//console.log(Math.floor(this.currentAudio.duration)); | |||||
// let playerLinks = document.querySelectorAll('.dd-player-link'); | |||||
// let self = this; | |||||
// playerLinks.forEach(function(pl){ | |||||
// pl.addEventListener('click', (e) => { | |||||
// self.container.classList.toggle('collapsed'); | |||||
// self.switcher.classList.toggle('opened'); | |||||
// }) | |||||
// }); | |||||
let wrap = document.createElement("div"); | |||||
wrap.setAttribute('id', 'wrap-playlist') | |||||
this.container.parentElement.appendChild(this.playerContainer); | |||||
this.playerContainer.appendChild(title); | |||||
this.playerContainer.appendChild(wrap); | |||||
wrap.appendChild(this.container); | |||||
}; | |||||
addProgressBar(){ | |||||
this.progressBar = document.createElement("progress"); | |||||
this.progressBar.setAttribute('value', 0); | |||||
this.startTimeInfo = document.createElement('div'); | |||||
this.endTimeInfo = document.createElement('div'); | |||||
let progressWrap = document.createElement("div"); | |||||
this.startTimeInfo.innerHTML = "00:00"; | |||||
this.endTimeInfo.innerHTML = "00:00"; | |||||
progressWrap.appendChild(this.startTimeInfo); | |||||
progressWrap.appendChild(this.progressBar); | |||||
progressWrap.appendChild(this.endTimeInfo); | |||||
this.startTimeInfo.classList.add('time-info'); | |||||
this.endTimeInfo.classList.add('time-info'); | |||||
progressWrap.classList.add('progress-wrap'); | |||||
this.playerContainer.append(progressWrap); | |||||
let self = this; | |||||
this.progressBar.addEventListener('click', (e) => { | |||||
if(self.currentAudio){ | |||||
self.currentAudio.currentTime = Math.floor(((e.layerX - self.progressBar.offsetLeft) * self.currentAudio.duration) / self.progressBar.offsetWidth); | |||||
self.progressBar.value = Math.floor(self.currentAudio.currentTime ); | |||||
} | |||||
}); | |||||
} | |||||
addPlayButton(){ | |||||
this.playButton = document.createElement("img"); | |||||
this.playButton.setAttribute('src','assets/images/play.png'); | |||||
this.playButton.setAttribute('id','play-button'); | |||||
let wrap = document.createElement("div"); | |||||
wrap.append(this.playButton); | |||||
this.container.append(wrap); | |||||
let self = this; | |||||
this.playButton.addEventListener('click', event => { | |||||
self.playing = !self.playing; | |||||
if(self.playing){ | |||||
self.currentAudio.play(); | |||||
self.currentAudio.parentElement.classList.add('playing'); | |||||
self.progressBar.setAttribute('max', Math.floor(self.currentAudio.duration)); | |||||
self.endTimeInfo.innerHTML = self.formatDuration(self.currentAudio.duration); | |||||
this.playButton.setAttribute('src','assets/images/pause.png'); | |||||
} | |||||
else{ | |||||
self.currentAudio.pause(); | |||||
self.currentAudio.parentElement.classList.remove('playing'); | |||||
this.playButton.setAttribute('src','assets/images/play.png'); | |||||
} | |||||
}) | |||||
} | |||||
bindAudioTags(){ | |||||
//let listElement = document.createElement("ul"); | |||||
let liTags = this.container.querySelectorAll('li') | |||||
let self = this; | |||||
let i = 0; | |||||
liTags.forEach(function(itemElement){ | |||||
// //let playBtn = document.createElement("img"); | |||||
// //playBtn.setAttribute('src','images/icons/play.png'); | |||||
// let itemElement = document.createElement("li"); | |||||
// //itemElement.appendChild(playBtn); | |||||
// itemElement.appendChild(document.createTextNode(track.title)); | |||||
// listElement.appendChild(itemElement); | |||||
// let audioElement = document.createElement('audio'); | |||||
// audioElement.setAttribute('src',track.src); | |||||
let audioTag = itemElement.querySelector('audio') | |||||
audioTag.addEventListener('timeupdate', (event) => { | |||||
self.audioTimeUpdate(audioTag); | |||||
}); | |||||
audioTag.addEventListener('ended', (event) => { | |||||
self.audioEnded(i); | |||||
}) | |||||
//itemElement.appendChild(audioElement); | |||||
audioTag.setAttribute('id','track-'+i); | |||||
if(i === 0) | |||||
self.currentAudio = audioTag; | |||||
itemElement.addEventListener('click', event => { | |||||
let items = self.container.getElementsByTagName('li'); | |||||
for(let i = 0; i < items.length; i++){ | |||||
items[i].classList.remove('playing'); | |||||
} | |||||
if(self.currentAudio) | |||||
self.currentAudio.pause(); | |||||
audioTag.play(); | |||||
self.playButton.setAttribute('src','assets/images/pause.png'); | |||||
self.playing = true; | |||||
itemElement.classList.add('playing'); | |||||
self.currentAudio = audioTag; | |||||
self.progressBar.setAttribute('max', Math.floor(self.currentAudio.duration)); | |||||
self.endTimeInfo.innerHTML = self.formatDuration(self.currentAudio.duration); | |||||
self.progressBar.setAttribute('value', 0); | |||||
}); | |||||
i++; | |||||
}); | |||||
} | |||||
audioTimeUpdate(audioElt){ | |||||
this.progressBar.value = Math.floor(audioElt.currentTime); | |||||
this.startTimeInfo.innerHTML = this.formatDuration(audioElt.currentTime); | |||||
} | |||||
audioEnded(n){ | |||||
let audios = document.getElementsByTagName('audio'); | |||||
let next = n == audios.length - 1 ? 0 : n + 1; | |||||
this.currentAudio.pause(); | |||||
this.currentAudio.currentTime = 0; | |||||
this.currentAudio = document.getElementsByTagName('audio')[next]; | |||||
this.currentAudio.parentElement.click(); | |||||
} | |||||
formatDuration(time){ | |||||
let s = parseInt(time% 60); | |||||
let m = parseInt((time / 60) % 60); | |||||
return (m < 10 ? ('0'+m) : m) + ':' + (s < 10 ? ('0'+s) : s); | |||||
} | |||||
} |
@@ -1,95 +0,0 @@ | |||||
import eyed3 | |||||
import os | |||||
import sys | |||||
import shutil | |||||
from html_writer import Html | |||||
# TracksDir class definition | |||||
class TracksDir: | |||||
def __init__(self, track_files): | |||||
self.trackFiles = track_files | |||||
# HTML Output | |||||
def to_html(self): | |||||
head = Html() | |||||
head.self_close_tag('meta', attributes=dict(charset='utf-8')) | |||||
head.self_close_tag('link', attributes=dict(href='assets/labelize.css', rel='stylesheet')) | |||||
body = Html() | |||||
track_number = 0 | |||||
with body.tag('div', classes=['track-list']): | |||||
# body.tag_with_content('Track List', name='h2') | |||||
with body.tag('div', attributes=dict(id='player-toggler')): | |||||
body.self_close_tag('img', attributes=dict(src='assets/labelize.png')) | |||||
with body.tag('ul') as list: | |||||
for t in self.trackFiles: | |||||
track_number = track_number + 1 | |||||
track_id = 'track-' + str(track_number) | |||||
audio_file = eyed3.load("build/" + t) | |||||
with body.tag('li') as li: | |||||
print(audio_file.tag.title) | |||||
li += str(audio_file.tag.title or 'untitled') + ' - ' | |||||
li += str(audio_file.tag.album or 'untitled') + ' - ' | |||||
li += audio_file.tag.artist | |||||
with body.tag('button', attributes=dict(onclick="togglePlay(this)")): | |||||
body.self_close_tag('img', attributes=dict(src='assets/play.png')) | |||||
with body.tag('audio', attributes=dict(src=t, id=track_id, | |||||
ontimeupdate="updateProgress(this)")) as audio: | |||||
audio += "Your browser does not support the audio element" | |||||
# with body.tag('canvas',attributes=dict(id='player-progress')) as canvas: | |||||
# canvas += "player's progress bar" | |||||
with body.tag('progress', attributes=dict(id='player-progress', value='0')) as canvas: | |||||
canvas += "player's progress bar" | |||||
with body.tag('div', attributes=dict(id='time-info')) as timeInfo: | |||||
timeInfo += '00:00' | |||||
with body.tag('script', attributes=dict(src='assets/labelize.js')) as script: | |||||
script += "" # script tag is not added without that trick | |||||
return Html.html_template(head, body).to_raw_html(indent_size=2) | |||||
# script usage function | |||||
def usage(): | |||||
print('USAGE : labelize.py [inputDirectory] [outputDirectory]') | |||||
# script beginning | |||||
arguments = len(sys.argv) - 1 | |||||
if arguments != 2: | |||||
usage() | |||||
sys.exit(2) | |||||
input_directory = sys.argv[1] | |||||
output_directory = sys.argv[2] | |||||
def copy_directory(src, dest): | |||||
try: | |||||
shutil.copytree(src, dest) | |||||
except shutil.Error as e: | |||||
print('Directory not copied. Error: %s' % e) | |||||
except OSError as e: | |||||
print('Directory not copied. Error: %s' % e) | |||||
# removes existing build directory if exists | |||||
if os.path.isdir(output_directory): | |||||
shutil.rmtree(output_directory) | |||||
# copies source files in the build directory | |||||
copy_directory(input_directory, 'build') | |||||
# copies assets in the build directory | |||||
copy_directory('assets', output_directory + '/assets') | |||||
for root, dirs, files in os.walk(input_directory): | |||||
trackFiles = [] | |||||
for f in files: | |||||
if f.lower().endswith(('.mp3', '.wav', '.ogg')): | |||||
trackFiles.append(f) | |||||
td = TracksDir(trackFiles) | |||||
f = open(output_directory + "/index.html", "w+") | |||||
f.write(td.to_html()) | |||||
f.close() |
@@ -1,2 +1,11 @@ | |||||
title: Clou | |||||
cover: image.png | |||||
band: Clou | |||||
images: | |||||
cover: image.png | |||||
contact: image2.png | |||||
albums_section : | |||||
title: CLOU albums | with LABELAR | |||||
albums: | |||||
- year : 2019 | |||||
title : p | |||||
- year : 2018 | |||||
title : x |
@@ -8,7 +8,7 @@ | |||||
<title>${title}</title> | <title>${title}</title> | ||||
<meta name="description" content="dedemo"> | <meta name="description" content="dedemo"> | ||||
<meta name="author" content="dede.space"> | <meta name="author" content="dede.space"> | ||||
<link rel="stylesheet" href="assets/css/dede-player.css"> | |||||
<link rel="stylesheet" href="assets/css/player.css"> | |||||
<link rel="stylesheet" href="assets/css/labelize.css"> | <link rel="stylesheet" href="assets/css/labelize.css"> | ||||
</head> | </head> | ||||
@@ -17,34 +17,34 @@ | |||||
<!-- <img src="images/CLOU_Icono-1.png"/> --> | <!-- <img src="images/CLOU_Icono-1.png"/> --> | ||||
</section> | </section> | ||||
<section class="dates"> | |||||
<h2><span class="scaps">${title}</span> live</h2> | |||||
</section> | |||||
<!-- <section class="dates">--> | |||||
<!-- <h2><span class="scaps">${title}</span> live</h2>--> | |||||
<!-- </section>--> | |||||
<section class="albums"> | <section class="albums"> | ||||
<h2>${albums_section['title']}</h2> | |||||
<ul> | |||||
% for a in albums_section['albums']: | |||||
<li>${a['year']} : ${a['title']}</li> | |||||
% endfor | |||||
</ul> | |||||
</section> | </section> | ||||
<section class="contact"> | <section class="contact"> | ||||
<span>contact<span class="point">@</span>clou<span class="point">.</span>space</span> | <span>contact<span class="point">@</span>clou<span class="point">.</span>space</span> | ||||
<footer class="flex-center"> | <footer class="flex-center"> | ||||
dede <span class='point'>.</span>space<span class='pipe'>|</span>2019 | |||||
dede<span class='point'>.</span>space<span class='pipe'>|</span>2019 | |||||
</footer> | </footer> | ||||
</section> | </section> | ||||
<div class="one-dpi" id="one-dpi" onclick="togglePlayer('player','one-dpi');"></div> | |||||
<ul id="player" data-title="Album title"> | |||||
% for t in tracks: | |||||
<li>${loop.index}<audio src="audio/01.mp3" id="track-${loop.index}"></audio></li> | |||||
% endfor | |||||
</ul> | |||||
<div class="player hidden" id="player"> | |||||
<h2>${title} - Audio</h2> | |||||
<ul> | |||||
% for a in tracks: | |||||
<li>Item ${loop.index}: ${a}</li> | |||||
% endfor | |||||
</ul> | |||||
</div> | |||||
<script src="./assets/js/labelize.js"></script> | |||||
<script type="module" src="./assets/js/labelize.js"></script> | |||||
</body> | </body> | ||||
</html> | </html> | ||||