Compare commits
7 Commits
119d4b2173
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d2b6c4748 | |||
| 63026da874 | |||
| 86f3dc9cfb | |||
| 9bcfb91408 | |||
| 1b9e7d64b0 | |||
| b83fbe9055 | |||
| 5534bdf42a |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -36,6 +36,8 @@ build/
|
|||||||
# VS Code
|
# VS Code
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
/backend/music/
|
||||||
|
|
||||||
# Temporary files
|
# Temporary files
|
||||||
*.tmp
|
*.tmp
|
||||||
*.swp
|
*.swp
|
||||||
|
|||||||
221
README.md
Normal file
221
README.md
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
# Music Portfolio
|
||||||
|
|
||||||
|
This project hosts a music portfolio with a **Node.js backend** serving music files and a **React frontend** displaying tracks. This guide explains how to run everything locally, convert music files to MP3, transfer files to your server, and set up the server with Nginx and SSL.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Prerequisites](#prerequisites)
|
||||||
|
2. [Local Setup](#local-setup)
|
||||||
|
3. [Convert Music to MP3](#convert-music-to-mp3)
|
||||||
|
4. [Deploy to Server](#deploy-to-server)
|
||||||
|
5. [Server Setup](#server-setup)
|
||||||
|
6. [Nginx Configuration](#nginx-configuration)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
* Node.js (v18+ recommended)
|
||||||
|
* npm / yarn
|
||||||
|
* Python3 + pip
|
||||||
|
* `pydub` Python library (for conversion)
|
||||||
|
* `ffmpeg` installed on both local and server machines
|
||||||
|
|
||||||
|
Install Python dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install pydub
|
||||||
|
```
|
||||||
|
|
||||||
|
Install Node dependencies in backend and frontend:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
npm install
|
||||||
|
|
||||||
|
cd ../frontend
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Local Setup
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
node server.js
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, the backend runs at:
|
||||||
|
|
||||||
|
```
|
||||||
|
http://localhost:3001
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, the frontend runs at:
|
||||||
|
|
||||||
|
```
|
||||||
|
http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Convert Music to MP3
|
||||||
|
|
||||||
|
All music files must be in the `backend/music` folder. The project includes a Python script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
python3 convert-to-mp3.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
|
||||||
|
* Convert `.flac`, `.wav`, `.m4a`, `.ogg` files to `.mp3`
|
||||||
|
* Delete the original files
|
||||||
|
* Keep the same filename but with `.mp3` extension
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deploy to Server
|
||||||
|
|
||||||
|
### Copy Backend & Music Files
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Replace <user> and <server-ip> with your server details
|
||||||
|
scp -r backend/* <user>@<server-ip>:/var/www/musicportfolio.keshavanand.net/backend/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Copy Frontend Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scp -r frontend/* <user>@<server-ip>:/var/www/musicportfolio.keshavanand.net/frontend/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Server Setup
|
||||||
|
|
||||||
|
### 1. Backend as a systemd service
|
||||||
|
|
||||||
|
Create `/etc/systemd/system/music-portfolio-backend.service`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=Music Portfolio Backend
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=www-data
|
||||||
|
WorkingDirectory=/var/www/musicportfolio.keshavanand.net/backend
|
||||||
|
ExecStart=/usr/bin/node server.js
|
||||||
|
Restart=always
|
||||||
|
Environment=PORT=3001
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable and start:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable music-portfolio-backend
|
||||||
|
sudo systemctl start music-portfolio-backend
|
||||||
|
sudo systemctl status music-portfolio-backend
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Nginx Configuration
|
||||||
|
|
||||||
|
Create `/etc/nginx/sites-available/musicportfolio.keshavanand.net`:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
server_name musicportfolio.keshavanand.net www.musicportfolio.keshavanand.net;
|
||||||
|
|
||||||
|
root /var/www/musicportfolio.keshavanand.net/frontend/dist;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://localhost:3001;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /music/ {
|
||||||
|
alias /var/www/musicportfolio.keshavanand.net/backend/music/;
|
||||||
|
autoindex off;
|
||||||
|
types {
|
||||||
|
audio/mpeg mp3;
|
||||||
|
audio/wav wav;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listen 443 ssl; # managed by Certbot
|
||||||
|
ssl_certificate /etc/letsencrypt/live/musicportfolio.keshavanand.net/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/musicportfolio.keshavanand.net/privkey.pem;
|
||||||
|
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||||
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name musicportfolio.keshavanand.net www.musicportfolio.keshavanand.net;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable site and reload:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ln -s /etc/nginx/sites-available/musicportfolio.keshavanand.net /etc/nginx/sites-enabled/
|
||||||
|
sudo nginx -t
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. SSL with Certbot
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install certbot python3-certbot-nginx
|
||||||
|
sudo certbot --nginx -d musicportfolio.keshavanand.net -d www.musicportfolio.keshavanand.net
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Permissions
|
||||||
|
|
||||||
|
Ensure the backend can read/write files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo chown -R www-data:www-data /var/www/musicportfolio.keshavanand.net
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
* Make sure the backend is running before loading the frontend.
|
||||||
|
* Frontend calls `/api/tracks` to fetch metadata.
|
||||||
|
* All music files must be in `.mp3` format for consistent browser support.
|
||||||
|
* Clear browser cache if changes aren’t reflected immediately.
|
||||||
43
backend/convert-to-mp3.py
Normal file
43
backend/convert-to-mp3.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# convert_to_mp3.py
|
||||||
|
import os
|
||||||
|
|
||||||
|
from pydub import AudioSegment
|
||||||
|
|
||||||
|
# Directory relative to this script
|
||||||
|
MUSIC_DIR = os.path.join(os.path.dirname(__file__), "music")
|
||||||
|
|
||||||
|
# Supported input formats
|
||||||
|
INPUT_FORMATS = [".flac", ".wav", ".m4a", ".ogg"]
|
||||||
|
|
||||||
|
|
||||||
|
def convert_to_mp3(file_path):
|
||||||
|
file_root, ext = os.path.splitext(file_path)
|
||||||
|
ext = ext.lower()
|
||||||
|
|
||||||
|
if ext not in INPUT_FORMATS:
|
||||||
|
return # skip unsupported formats
|
||||||
|
|
||||||
|
mp3_path = f"{file_root}.mp3"
|
||||||
|
|
||||||
|
print(f"Converting {file_path} → {mp3_path}")
|
||||||
|
|
||||||
|
# Load audio
|
||||||
|
audio = AudioSegment.from_file(file_path)
|
||||||
|
|
||||||
|
# Export as MP3
|
||||||
|
audio.export(mp3_path, format="mp3", bitrate="320k")
|
||||||
|
|
||||||
|
# Delete original file
|
||||||
|
os.remove(file_path)
|
||||||
|
print(f"Deleted original: {file_path}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
for root, _, files in os.walk(MUSIC_DIR):
|
||||||
|
for file in files:
|
||||||
|
full_path = os.path.join(root, file)
|
||||||
|
convert_to_mp3(full_path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "music-backend",
|
"name": "music-backend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server.js",
|
"start": "node server.js",
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
// server.js
|
import express from 'express';
|
||||||
const express = require('express');
|
import path from 'path';
|
||||||
const path = require('path');
|
import fs from 'fs/promises';
|
||||||
const fs = require('fs/promises');
|
import * as mm from 'music-metadata';
|
||||||
const mm = require('music-metadata');
|
import cors from 'cors';
|
||||||
const cors = require('cors');
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(cors()); // adjust for production if needed
|
app.use(cors()); // adjust for production if needed
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ export default function App() {
|
|||||||
|
|
||||||
// Hard-coded featured tracks (filenames only)
|
// Hard-coded featured tracks (filenames only)
|
||||||
const FEATURED = [
|
const FEATURED = [
|
||||||
"Kanne Kalimaane__Moondram Pirai__Unplucked Instrumental Studio Music Cover__2025-06-26.flac",
|
"Kanne Kalimaane__Moondram Pirai__Unplucked Instrumental Studio Music Cover__2025-06-26.mp3",
|
||||||
"Pudhu Vellai Mazhai__Roja__Unplucked Instrumental Studio Music Cover__2025-03-15.flac",
|
"Pudhu Vellai Mazhai__Roja__Unplucked Instrumental Studio Music Cover__2025-03-15.mp3",
|
||||||
"Porkalam and Kannana Kanney Medley__Thenali and Viswasam__Unplucked Instrumental Studio Music Cover__2025-01-01.flac"
|
"Porkalam and Kannana Kanney Medley__Thenali and Viswasam__Unplucked Instrumental Studio Music Cover__2025-01-01.mp3"
|
||||||
];
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
axios.get("http://localhost:3001/api/tracks").then(res => {
|
axios.get('/api/tracks').then(res => {
|
||||||
setAllTracks(res.data.tracks);
|
setAllTracks(res.data.tracks);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
@@ -66,8 +66,8 @@ export default function App() {
|
|||||||
<div>
|
<div>
|
||||||
<header>
|
<header>
|
||||||
<h1>Keshav’s Music Portfolio</h1>
|
<h1>Keshav’s Music Portfolio</h1>
|
||||||
<p style={{ marginTop: "20px", marginBottom: "20px" }}>
|
<p style={{ marginTop: "20px", marginBottom: "20px" }}> </p>
|
||||||
</p>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
@@ -75,6 +75,7 @@ export default function App() {
|
|||||||
{featuredTracks.length > 0 && (
|
{featuredTracks.length > 0 && (
|
||||||
<section>
|
<section>
|
||||||
<h2>Featured</h2>
|
<h2>Featured</h2>
|
||||||
|
<p style={{ marginTop: "20px", marginBottom: "20px" }}> </p>
|
||||||
<div className="featured-container">
|
<div className="featured-container">
|
||||||
{featuredTracks.map(t => (
|
{featuredTracks.map(t => (
|
||||||
<TrackCard key={t.url} t={t} featured={true} />
|
<TrackCard key={t.url} t={t} featured={true} />
|
||||||
|
|||||||
Reference in New Issue
Block a user