| @@ -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> | ||||