ソースを参照

UI stuff + audio player implementation

v2
choj 4年前
コミット
8e6f937efb
36個のファイルの変更348行の追加195行の削除
  1. +20
    -2
      README.md
  2. +67
    -27
      assets/css/labelize.css
  3. +2
    -0
      assets/css/player.css
  4. バイナリ
      assets/fonts/noto-sans/NotoSans-Bold.ttf
  5. バイナリ
      assets/fonts/noto-sans/NotoSans-BoldItalic.ttf
  6. バイナリ
      assets/fonts/noto-sans/NotoSans-Italic.ttf
  7. バイナリ
      assets/fonts/noto-sans/NotoSans-Regular.ttf
  8. +2
    -1
      assets/fonts/work-sans/SIL Open Font License.txt
  9. バイナリ
      assets/fonts/work-sans/WorkSans-Black.otf
  10. バイナリ
      assets/fonts/work-sans/WorkSans-BlackItalic.otf
  11. バイナリ
      assets/fonts/work-sans/WorkSans-Bold.otf
  12. バイナリ
      assets/fonts/work-sans/WorkSans-BoldItalic.otf
  13. バイナリ
      assets/fonts/work-sans/WorkSans-ExtraBold.otf
  14. バイナリ
      assets/fonts/work-sans/WorkSans-ExtraBoldItalic.otf
  15. バイナリ
      assets/fonts/work-sans/WorkSans-ExtraLight.otf
  16. バイナリ
      assets/fonts/work-sans/WorkSans-ExtraLightItalic.otf
  17. バイナリ
      assets/fonts/work-sans/WorkSans-Hairline.otf
  18. バイナリ
      assets/fonts/work-sans/WorkSans-Italic.otf
  19. バイナリ
      assets/fonts/work-sans/WorkSans-Light.otf
  20. バイナリ
      assets/fonts/work-sans/WorkSans-LightItalic.otf
  21. バイナリ
      assets/fonts/work-sans/WorkSans-Medium.otf
  22. バイナリ
      assets/fonts/work-sans/WorkSans-MediumItalic.otf
  23. バイナリ
      assets/fonts/work-sans/WorkSans-Regular.otf
  24. バイナリ
      assets/fonts/work-sans/WorkSans-SemiBold.otf
  25. バイナリ
      assets/fonts/work-sans/WorkSans-SemiBoldItalic.otf
  26. バイナリ
      assets/fonts/work-sans/WorkSans-Thin.otf
  27. バイナリ
      assets/fonts/work-sans/WorkSans-ThinItalic.otf
  28. バイナリ
      assets/fonts/work-sans/worksans-italic-vf.ttf
  29. バイナリ
      assets/fonts/work-sans/worksans-roman-vf.ttf
  30. バイナリ
      assets/images/pause.png
  31. バイナリ
      assets/images/play.png
  32. +55
    -50
      assets/js/labelize.js
  33. +173
    -0
      assets/js/player.js
  34. +0
    -95
      labelize.py
  35. +11
    -2
      labelize.yaml
  36. +18
    -18
      template.html

+ 20
- 2
README.md ファイルの表示

@@ -1,6 +1,6 @@
# 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

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

```
$ python labelize.py inputDirectory outputDirectory
$ python labelize.py [inputDirectory] [outputDirectory]
```

# Credentials


+ 67
- 27
assets/css/labelize.css ファイルの表示

@@ -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;
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{
height: 100vh;
}

section h2 {
font-size: 2rem;
}

section:nth-of-type(odd){
background-color: white;
color: blue;
color: var(--secondary-color);
}

section:nth-of-type(even){
background-color: blue;
background-color: var(--secondary-color);
color: white;
}

.cover{
section.cover{
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;
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;
}
.player li {
height: 10vh;

section.contact{
display : flex;
flex-direction: column;
justify-content: 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{


assets/css/dede-player.css → assets/css/player.css ファイルの表示

@@ -30,6 +30,7 @@
height:var(--one-dpi);
background-color: white;
cursor: pointer;
z-index:10;
}

.dd-player-switch.opened{
@@ -47,6 +48,7 @@
margin-left : calc(var(--one-dpi) / 2);
color: var(--dd-blue);
font-size: 1.5rem;
list-style-type : none;
}

.dd-player ul li img{

バイナリ
assets/fonts/noto-sans/NotoSans-Bold.ttf ファイルの表示


バイナリ
assets/fonts/noto-sans/NotoSans-BoldItalic.ttf ファイルの表示


バイナリ
assets/fonts/noto-sans/NotoSans-Italic.ttf ファイルの表示


バイナリ
assets/fonts/noto-sans/NotoSans-Regular.ttf ファイルの表示


assets/fonts/noto-sans/SIL Open Font License.txt → assets/fonts/work-sans/SIL Open Font License.txt ファイルの表示

@@ -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 license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL

バイナリ
assets/fonts/work-sans/WorkSans-Black.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-BlackItalic.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-Bold.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-BoldItalic.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-ExtraBold.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-ExtraBoldItalic.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-ExtraLight.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-ExtraLightItalic.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-Hairline.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-Italic.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-Light.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-LightItalic.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-Medium.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-MediumItalic.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-Regular.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-SemiBold.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-SemiBoldItalic.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-Thin.otf ファイルの表示


バイナリ
assets/fonts/work-sans/WorkSans-ThinItalic.otf ファイルの表示


バイナリ
assets/fonts/work-sans/worksans-italic-vf.ttf ファイルの表示


バイナリ
assets/fonts/work-sans/worksans-roman-vf.ttf ファイルの表示


バイナリ
assets/images/pause.png ファイルの表示

変更前 変更後
幅: 640  |  高さ: 640  |  サイズ: 17 KiB 幅: 48  |  高さ: 48  |  サイズ: 225 B

バイナリ
assets/images/play.png ファイルの表示

変更前 変更後
幅: 640  |  高さ: 640  |  サイズ: 19 KiB 幅: 48  |  高さ: 48  |  サイズ: 299 B

+ 55
- 50
assets/js/labelize.js ファイルの表示

@@ -1,7 +1,12 @@
var current = undefined
const progressMargin = 50;
var progress = undefined;
import DedePlayer from './player.js';

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');
// document.getElementById('player-progress').addEventListener('click', (e) => {
// if(current){
@@ -16,50 +21,50 @@ window.addEventListener('DOMContentLoaded', (event) => {
// toggler.addEventListener('click', (e) => {
// 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');
//}

+ 173
- 0
assets/js/player.js ファイルの表示

@@ -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);
}


}

+ 0
- 95
labelize.py ファイルの表示

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

+ 11
- 2
labelize.yaml ファイルの表示

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

+ 18
- 18
template.html ファイルの表示

@@ -8,7 +8,7 @@
<title>${title}</title>
<meta name="description" content="dedemo">
<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">
</head>

@@ -17,34 +17,34 @@

<!-- <img src="images/CLOU_Icono-1.png"/> -->
</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">

<h2>${albums_section['title']}</h2>
<ul>
% for a in albums_section['albums']:
<li>${a['year']} : ${a['title']}</li>
% endfor
</ul>
</section>



<section class="contact">
<span>contact<span class="point">@</span>clou<span class="point">.</span>space</span>
<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>
</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>
</html>



読み込み中…
キャンセル
保存