a better POST /entry treatment.. + some updates
This commit is contained in:
parent
19ed159164
commit
333a11fcfb
6 changed files with 391 additions and 44 deletions
1
public/stream.m3u
Normal file
1
public/stream.m3u
Normal file
|
|
@ -0,0 +1 @@
|
|||
http://116.122.163.142:8000/stream
|
||||
118
server.js
118
server.js
|
|
@ -57,10 +57,13 @@ fastify.get("/", function (request, reply) {
|
|||
reply.view("/src/pages/parade.html", {});
|
||||
});
|
||||
|
||||
//get '/preview/:foldername'
|
||||
//get '/live
|
||||
fastify.get("/live", function (request, reply) {
|
||||
reply.view("/src/pages/live.html", {});
|
||||
});
|
||||
|
||||
//get '/preview/:foldername' --> request.params.foldername
|
||||
fastify.get("/preview/:foldername", function (request, reply) {
|
||||
//
|
||||
//request.params.foldername
|
||||
reply.view("/src/pages/preview.html", {});
|
||||
});
|
||||
|
||||
|
|
@ -79,7 +82,12 @@ fastify.get("/preview/:foldername", function (request, reply) {
|
|||
|
||||
let folders = [];
|
||||
for (const item of list) {
|
||||
var fields = JSON.parse((await fs.readFile('/media/storage/public/sound-parade/' + item + '/fields.json')).toString('utf8'));
|
||||
let json = await fs.readFile('/media/storage/public/sound-parade/' + item + '/fields.json')
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
if (json != undefined) {
|
||||
var fields = JSON.parse(json.toString('utf8'));
|
||||
folders.push({
|
||||
foldername: item,
|
||||
group: fields.group,
|
||||
|
|
@ -87,6 +95,7 @@ fastify.get("/preview/:foldername", function (request, reply) {
|
|||
comment: fields.comment,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(folders);
|
||||
|
||||
|
|
@ -172,19 +181,73 @@ fastify.get("/delete/:foldername/:pass", async function (request, reply) {
|
|||
fastify.post("/entry", async function (request, reply) {
|
||||
|
||||
// stores files to tmp dir and return paths
|
||||
const files = await request.saveRequestFiles();
|
||||
const files = await request.saveRequestFiles().catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
let audiofile = files.find(f => f.fieldname == 'audiofile');
|
||||
let pixelfile = files.find(f => f.fieldname == 'pixels');
|
||||
let tmpdir = path.dirname(audiofile.filepath);
|
||||
// console.log(audiofile.fields.message.value);
|
||||
|
||||
console.log("hi.");
|
||||
console.log("-- hi."); // got all files.
|
||||
|
||||
//convert to mp3 ?
|
||||
var converted = false;
|
||||
//conversion needed?
|
||||
var conversion = false;
|
||||
if (path.extname(audiofile.filename) !== ".mp3") {
|
||||
console.log("convert to mp3...");
|
||||
converted = true;
|
||||
conversion = true;
|
||||
console.log("-- well.."); //conversion. is scheduled.
|
||||
} else {
|
||||
console.log("-- good"); //no conversion_
|
||||
}
|
||||
|
||||
//upload
|
||||
const client = new Client(server);
|
||||
|
||||
console.log("-- ready"); //file server opened
|
||||
|
||||
//create unique folder ==> timestamp + uuid
|
||||
const folder = await client.createFolder("Storage/public/sound-parade/" + moment().tz('Asia/Seoul').format('YYYYMMDD-HHmmss-') + uuidv1());
|
||||
//
|
||||
const json = await folder.createFile("fields.json", Buffer.from(JSON.stringify({
|
||||
group: audiofile.fields.group.value,
|
||||
title: audiofile.fields.title.value,
|
||||
comment: audiofile.fields.comment.value,
|
||||
pass: audiofile.fields.pass.value
|
||||
})));
|
||||
const image = await folder.createFile("pixels.png", await fs.readFile(pixelfile.filepath));
|
||||
//
|
||||
if (conversion) {
|
||||
console.log('---- hi conv');
|
||||
//save original file as is. + we have scheduled a conversion.
|
||||
let afile = await fs.readFile(audiofile.filepath)
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
//
|
||||
if (afile != undefined) {
|
||||
const file = await folder.createFile(audiofile.filename, afile)
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
console.log('---- createFile err.');
|
||||
console.log('afile', afile);
|
||||
});
|
||||
} else {
|
||||
console.log('afile == undef!');
|
||||
}
|
||||
console.log('---- yes conv');
|
||||
//
|
||||
} else {
|
||||
console.log('---- no conv');
|
||||
//rename & save original file.
|
||||
const file = await folder.createFile("audio.mp3", await fs.readFile(audiofile.filepath).catch(err => console.error(err)));
|
||||
}
|
||||
|
||||
console.log("-- saved"); //saved in file server.
|
||||
|
||||
//conversion needed?
|
||||
var converted = false;
|
||||
if (conversion) {
|
||||
console.log("-- mp3..."); //converting to mp3...
|
||||
function converter() {
|
||||
return new Promise((resolve, reject) => {
|
||||
//
|
||||
|
|
@ -193,9 +256,12 @@ fastify.post("/entry", async function (request, reply) {
|
|||
ffmpeg()
|
||||
.addInput(audiofile.filepath)
|
||||
.on("error", function(err) {
|
||||
console.log("-- err:", err);
|
||||
reject(err);
|
||||
})
|
||||
.on("end", function() {
|
||||
console.log("-- fine."); //conversion succeesful
|
||||
converted = true;
|
||||
resolve(outputFile);
|
||||
})
|
||||
.outputOptions('-b:a 192000')
|
||||
|
|
@ -203,33 +269,16 @@ fastify.post("/entry", async function (request, reply) {
|
|||
.run();
|
||||
});
|
||||
}
|
||||
await converter();
|
||||
// console.log(await fs.readdir(tmpdir));
|
||||
}
|
||||
await converter().catch((err) => { console.error(err); });
|
||||
|
||||
console.log("ok.");
|
||||
|
||||
//upload
|
||||
const client = new Client(server);
|
||||
|
||||
console.log("good.");
|
||||
|
||||
//create unique folder ==> timestamp + uuid
|
||||
const folder = await client.createFolder("Storage/public/sound-parade/" + moment().tz('Asia/Seoul').format('YYYYMMDD-HHmmss-') + uuidv1());
|
||||
if (converted) {
|
||||
const file = await folder.createFile("audio.mp3", await fs.readFile(tmpdir + '/converted.mp3'));
|
||||
} else {
|
||||
const file = await folder.createFile("audio.mp3", await fs.readFile(audiofile.filepath));
|
||||
console.log("-- done");
|
||||
}
|
||||
const image = await folder.createFile("pixels.png", await fs.readFile(pixelfile.filepath));
|
||||
const json = await folder.createFile("fields.json", Buffer.from(JSON.stringify({
|
||||
group: audiofile.fields.group.value,
|
||||
title: audiofile.fields.title.value,
|
||||
comment: audiofile.fields.comment.value,
|
||||
pass: audiofile.fields.pass.value
|
||||
})));
|
||||
|
||||
console.log("done.");
|
||||
}
|
||||
|
||||
console.log("-- well done");
|
||||
|
||||
reply.send('done!');
|
||||
//reply.redirect('/submit');
|
||||
|
|
@ -257,6 +306,11 @@ io.on("connection", function(socket) {
|
|||
fn(false);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("flow", function(req) {
|
||||
io.emit("flow", req);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
//
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
<div class="bg"></div>
|
||||
<div class="content">
|
||||
<div class="first"><a href="/entry">KR</a></div>
|
||||
<div class="second"><a href="/">Home</a></div>
|
||||
<div class="second"><a href="/">parade</a></div>
|
||||
<div class="notice">
|
||||
<section>
|
||||
<h1>Walking Towards the Flow</h1>
|
||||
|
|
@ -157,6 +157,9 @@
|
|||
<input id="submit" type="submit" value="Upload" />
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
↓
|
||||
</p>
|
||||
</form>
|
||||
</section>
|
||||
<section>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
<div class="bg"></div>
|
||||
<div class="content">
|
||||
<div class="first"><a href="/en/entry">EN</a></div>
|
||||
<div class="second"><a href="/" target="_blank">홈</a></div>
|
||||
<div class="second"><a href="/" target="_blank">퍼레이드</a></div>
|
||||
<div class="notice">
|
||||
<section>
|
||||
<h1>흐름을 향하여 걷는</h1>
|
||||
|
|
@ -159,6 +159,9 @@
|
|||
<input id="submit" type="submit" value="보내기!" />
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
↓
|
||||
</p>
|
||||
</form>
|
||||
</section>
|
||||
<section>
|
||||
|
|
|
|||
285
src/pages/live.html
Normal file
285
src/pages/live.html
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>흐름을 향하여 걷는</title>
|
||||
<link rel="stylesheet" href="/default.css" />
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
<script src="/js/p5-v1.1.9.min.js"></script>
|
||||
<script src="/js/socket-v2.3.0.io.slim.js"></script>
|
||||
<script src="/js/Tone-14.8.36.min.js"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@500&display=swap" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="bg"></div>
|
||||
<div class="content">
|
||||
</div>
|
||||
<script>
|
||||
// // force https
|
||||
// var http_confirm = location.href.split(":")[0];
|
||||
// if (http_confirm == "http") {
|
||||
// window.location.replace("https://" + location.host);
|
||||
// }
|
||||
|
||||
//
|
||||
var socket = io(location.host);
|
||||
var n = 0;
|
||||
var fr = 20;
|
||||
var arr = [];
|
||||
var looper;
|
||||
var score;
|
||||
var logo;
|
||||
var silence;
|
||||
var clap;
|
||||
|
||||
//promisify -> new Tone.Player
|
||||
function AudioImport(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var audio = new Tone.Player(url, () => resolve(audio));
|
||||
});
|
||||
}
|
||||
|
||||
async function setup() {
|
||||
noCanvas();
|
||||
if (windowWidth > 1500 && windowWidth > windowHeight) {
|
||||
fr = 30;
|
||||
} else {
|
||||
fr = 20;
|
||||
}
|
||||
frameRate(fr);
|
||||
|
||||
//p5 'draw()' doesn't work if user is not looking at the tab.
|
||||
noLoop();
|
||||
// --> use custom looper.
|
||||
}
|
||||
|
||||
//
|
||||
var myroom = -1;
|
||||
var intro;
|
||||
var ready;
|
||||
//
|
||||
socket.on("connect", async function() {
|
||||
console.log("connected!");
|
||||
|
||||
//
|
||||
silence = (await AudioImport("/audio/_silence.wav")).toDestination();
|
||||
clap = (await AudioImport("/audio/clap01.mp3")).toDestination();
|
||||
|
||||
//TESTING... fixed to room 1.
|
||||
// myroom = 1;
|
||||
|
||||
if (myroom == -1 && selectAll(".roomsel").length == 0) {
|
||||
//initial connection -> ask the room number.
|
||||
|
||||
var roomsel = createDiv();
|
||||
roomsel.class("roomsel");
|
||||
var b = createButton("라이브! Live Performance-", 1);
|
||||
var d = createDiv("흐름을 향하여 걷는 Walking towards the Flow");
|
||||
d.class("title");
|
||||
b.mouseClicked(function() {
|
||||
silence.start();
|
||||
//clap.start();
|
||||
|
||||
setTimeout(() => {
|
||||
selectAll(".roomsel").forEach(item => {
|
||||
item.remove();
|
||||
// 1 second popup '.intro' div
|
||||
intro = createDiv(
|
||||
"라이브<br>«흐름을 향하여 걷는»은 온라인 공간에 소리의 행렬을 만드는 사운드 퍼레이드입니다. 누구나 참여할 수 있는 이 퍼레이드는 여러분이 보내주시는 소리가 모여 만들어집니다.<br>소리가 들리지 않는다면, 볼륨을 확인해주시고, 스마트폰 환경에서는 진동해제해주세요. <br><br>Live Performance<br>«Walking Toward the Flow» is a sound parade that creates a procession of sounds in the online space. Anyone can participate. This parade is made by collecting the sounds you sent.<br>If there is no sound, check the volume and turn off the vibration in the smartphone environment."
|
||||
);
|
||||
intro.class("notice intro"); //-> fadeout & disapear by css animation style.
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
roomsel.child(d);
|
||||
roomsel.child(b);
|
||||
} else {
|
||||
//re-connection -> just connect to remembered room!
|
||||
socket.emit("room", myroom, function(res) {
|
||||
if (res) {
|
||||
console.log("entered the room -> " + myroom);
|
||||
} else {
|
||||
console.log("rejected!");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//var fading_factor = 0.3; //30%
|
||||
var fading_factor = 0.5; //50%
|
||||
socket.on("flow", async function(flow) {
|
||||
|
||||
console.log(flow);
|
||||
var list = await new Promise((resolve, reject) => {
|
||||
loadJSON("/entries", (json) => resolve(json));
|
||||
})
|
||||
console.log(list);
|
||||
|
||||
// var object = flow.object;
|
||||
var object = {
|
||||
"id": 1,
|
||||
"type": "abc",
|
||||
"src": "https://p.dianaband.info/public/sound-parade/" + list[flow] + "/pixels.png",
|
||||
"audio": "https://p.dianaband.info/public/sound-parade/" + list[flow] + "/audio.mp3",
|
||||
"alt": "알트",
|
||||
"size": {
|
||||
"base": 40,
|
||||
"random": 20
|
||||
},
|
||||
"y": {
|
||||
"base": 20,
|
||||
"random": 10
|
||||
},
|
||||
"showtime": 20000
|
||||
};
|
||||
console.log(object);
|
||||
|
||||
var img = createImg(object.src, object.alt, "", async function(im) {
|
||||
//로딩이 끝나면, start!
|
||||
var pv = new Tone.PanVol(0, -99).toDestination();
|
||||
var snd = await AudioImport(object.audio); // NOTE: url with spaces didn't work here.
|
||||
snd.connect(pv).start();
|
||||
snd.loop = true;
|
||||
|
||||
//로딩이 끝나면, show!
|
||||
im.show();
|
||||
|
||||
//그림의 크기와 초기 위치 ==> 가로 보기인 경우
|
||||
var width = 0;
|
||||
if (windowWidth > windowHeight) {
|
||||
|
||||
width = (windowHeight * (object.size.base * 1.4 + object.size.random * Math.random())) / 100; // 좀더 크게 + 40% (ratio)
|
||||
im.size(width, AUTO);
|
||||
|
||||
im.position(
|
||||
windowWidth * (1 + fading_factor),
|
||||
(windowHeight * (object.y.base + object.y.random * Math.random()) * 0.5) / 100 // 좀더 위로 위로 - 50% (ratio)
|
||||
);
|
||||
|
||||
//그림의 크기와 초기 위치 ==> 세로 보기인 경우
|
||||
} else {
|
||||
|
||||
width = (windowHeight * (object.size.base + object.size.random * Math.random())) / 100; // json에서 정한 크기 그대로. (ratio)
|
||||
im.size(width, AUTO);
|
||||
|
||||
im.position(
|
||||
windowWidth * (1 + fading_factor),
|
||||
(windowHeight * (object.y.base + object.y.random * Math.random())) / 100 // json에서 정한 위치 그대로. (ratio)
|
||||
);
|
||||
}
|
||||
|
||||
//추가 정보들
|
||||
im.attribute("data-type", object.type);
|
||||
im.attribute("data-showtime", object.showtime / 1000); //milli-sec. -> seconds.
|
||||
|
||||
//'아이콘' 들은 애니메이션을 시켜줘야 함...
|
||||
if (object.type == "icon") {
|
||||
//
|
||||
im.class("rotate");
|
||||
im.style("animation-duration", object.rotate + "s");
|
||||
var orgs = im.style("transform-origin").split(" ");
|
||||
var str = parseFloat(orgs[0]) + object.pivot.x + "px";
|
||||
str = str + " " + parseFloat(orgs[1]) + object.pivot.y + "px";
|
||||
im.style("transform-origin", str);
|
||||
//
|
||||
}
|
||||
|
||||
//로딩이 다 되면, rendering array에 추가.
|
||||
var bundle = {
|
||||
ref: object,
|
||||
img: im,
|
||||
sound: snd,
|
||||
panvol: pv,
|
||||
width: width
|
||||
};
|
||||
arr.push(bundle);
|
||||
});
|
||||
|
||||
//첨에는 hide
|
||||
img.hide();
|
||||
|
||||
});
|
||||
|
||||
//p5 'draw()' doesn't work if user is not looking at the tab.
|
||||
Tone.Transport.scheduleRepeat((time) => {
|
||||
|
||||
//
|
||||
for (var i = arr.length - 1; i >= 0; i -= 1) {
|
||||
var bundle = arr[i];
|
||||
var img = bundle.img;
|
||||
var showtime = parseFloat(img.attribute("data-showtime"));
|
||||
var type = img.attribute("data-type");
|
||||
var x = img.position().x;
|
||||
var y = img.position().y;
|
||||
y = y + random(-1, 1);
|
||||
x = x - windowWidth / (fr * showtime);
|
||||
|
||||
//
|
||||
if (type == "icon") {
|
||||
img.style("z-index", "-1");
|
||||
}
|
||||
|
||||
img.position(x, y);
|
||||
var pan = (x / windowWidth) * 2 - 1;
|
||||
|
||||
//panning
|
||||
var snd = bundle.sound;
|
||||
var pv = bundle.panvol;
|
||||
if (x >= -bundle.width && x < windowWidth) {
|
||||
pan = ((x + bundle.width) / (windowWidth + bundle.width)) * 2 - 1;
|
||||
pv.pan.value = pan;
|
||||
pv.volume.value = 0;//(dB)
|
||||
} else {
|
||||
var range;
|
||||
var knob;
|
||||
if (x >= windowWidth) {
|
||||
range = windowWidth * fading_factor
|
||||
knob = x - windowWidth;
|
||||
pv.pan.value = 1;
|
||||
pv.volume.value = knob / range * -20;
|
||||
} else if (x < -bundle.width) {
|
||||
range = windowWidth * fading_factor
|
||||
knob = (x + bundle.width) * -1;
|
||||
pv.pan.value = -1;
|
||||
pv.volume.value = knob / range * -20;
|
||||
}
|
||||
}
|
||||
|
||||
//remove with sound fade-out
|
||||
var exit_x = -bundle.width - windowWidth * fading_factor;
|
||||
if (x < exit_x) {
|
||||
img.remove();
|
||||
snd.stop();
|
||||
delete snd;
|
||||
delete pv;
|
||||
arr.splice(i, 1);
|
||||
}
|
||||
}
|
||||
//
|
||||
|
||||
}, 1.0/fr);
|
||||
Tone.Transport.start()
|
||||
|
||||
// function randomvoiceplay() {
|
||||
// (looper = function(timeout) {
|
||||
// setTimeout(function() {
|
||||
// voice[int(random(19))].play();
|
||||
// looper(random(8000, 12000));
|
||||
// }, timeout);
|
||||
// })(8000);
|
||||
// }
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -312,7 +312,6 @@
|
|||
if (type == "icon") {
|
||||
img.style("z-index", "-1");
|
||||
}
|
||||
3;
|
||||
|
||||
img.position(x, y);
|
||||
var pan = (x / windowWidth) * 2 - 1;
|
||||
|
|
@ -350,7 +349,9 @@
|
|||
arr.splice(i, 1);
|
||||
if (arr.length == 0 && playing == false) {
|
||||
setTimeout(() => {
|
||||
createDiv("미리보기가 끝났습니다. <br>Preview is over.").class("notice").style('text-align', 'center');
|
||||
createDiv("미리보기가 끝났습니다. <br>Preview is over.<br><br>").class("notice").style('text-align', 'center')
|
||||
.child(createButton("퍼레이드 가기").attribute('onclick', 'location.href="/"').style('margin', '1em 1em'))
|
||||
.child(createButton("닫기").attribute('onclick', 'window.close()').style('margin', '1em 1em'))
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue