Project structure:

LitMusic/
├── static/
│   ├── app.js
│   └── styles.css
├── templates/
│   └── index.html
├── app.py
├── database.db
├── database.py
└── requirements.txt


Project files:

app.py
```py
from flask import Flask, request, jsonify, send_from_directory, render_template
from flask_cors import CORS
from database import Database

app = Flask(__name__, static_folder='static', template_folder='templates')
CORS(app)

db = Database()

# Главная страница
@app.route('/')
def index():
    return render_template('index.html')

# Обслуживание статических файлов
@app.route('/static/<path:path>')
def send_static(path):
    return send_from_directory('static', path)

# 1. Получение всех треков (с пагинацией)
@app.route('/tracks', methods=['GET'])
def get_tracks():
    page = request.args.get('page', 1, type=int)
    per_page = 10
    tracks, total = db.get_tracks(page, per_page)
    return jsonify({'tracks': tracks, 'total': total})

# 2. Поиск треков
@app.route('/tracks/search', methods=['GET'])
def search_tracks():
    description = request.args.get('description', '', type=str)
    tags = request.args.getlist('tags')
    tracks = db.search_tracks(description, tags)
    return jsonify({'tracks': tracks})

# 3. Добавление нового трека
@app.route('/tracks', methods=['POST'])
def add_track():
    data = request.get_json()
    name = data.get('name')
    description = data.get('description')
    url = data.get('url')
    tags = data.get('tags', [])

    if not name or not url:
        return jsonify({'error': 'Название и ссылка на трек обязательны.'}), 400

    track_id = db.add_track(name, description, url, tags)

    return jsonify({'message': 'Трек успешно добавлен.', 'track_id': track_id}), 201

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=6390)
```


database.py
```py
import sqlite3

class Database:
    def __init__(self, db_path='database.db'):
        self.db_path = db_path
        self.conn = sqlite3.connect(self.db_path, check_same_thread=False)
        self.init_db()

    def init_db(self):
        cursor = self.conn.cursor()
        # Создание таблицы треков
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS tracks (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL,
                description TEXT,
                url TEXT NOT NULL
            )
        ''')
        # Создание таблицы тегов
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS tags (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL UNIQUE
            )
        ''')
        # Создание связывающей таблицы
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS track_tags (
                track_id INTEGER,
                tag_id INTEGER,
                FOREIGN KEY(track_id) REFERENCES tracks(id),
                FOREIGN KEY(tag_id) REFERENCES tags(id),
                PRIMARY KEY(track_id, tag_id)
            )
        ''')
        self.conn.commit()

    def add_track(self, name, description, url, tags):
        cursor = self.conn.cursor()
        cursor.execute('INSERT INTO tracks (name, description, url) VALUES (?, ?, ?)', (name, description, url))
        track_id = cursor.lastrowid
        for tag_name in tags:
            # Проверяем, существует ли тег
            cursor.execute('SELECT id FROM tags WHERE name = ?', (tag_name,))
            tag_row = cursor.fetchone()
            if tag_row:
                tag_id = tag_row[0]
            else:
                cursor.execute('INSERT INTO tags (name) VALUES (?)', (tag_name,))
                tag_id = cursor.lastrowid
            # Добавляем связь трека и тега
            cursor.execute('INSERT OR IGNORE INTO track_tags (track_id, tag_id) VALUES (?, ?)', (track_id, tag_id))
        self.conn.commit()
        return track_id

    def get_tracks(self, page=1, per_page=10):
        cursor = self.conn.cursor()
        offset = (page - 1) * per_page
        cursor.execute('SELECT * FROM tracks LIMIT ? OFFSET ?', (per_page, offset))
        tracks_rows = cursor.fetchall()
        tracks = []
        for row in tracks_rows:
            track = {
                'id': row[0],
                'name': row[1],
                'description': row[2],
                'url': row[3],
                'tags': self.get_tags_for_track(row[0])
            }
            tracks.append(track)
        # Получаем общее количество треков
        cursor.execute('SELECT COUNT(*) FROM tracks')
        total = cursor.fetchone()[0]
        return tracks, total

    def search_tracks(self, description='', tags=[]):
        cursor = self.conn.cursor()
        query = 'SELECT DISTINCT t.id, t.name, t.description, t.url FROM tracks t'
        params = []
        if tags:
            query += ' INNER JOIN track_tags tt ON t.id = tt.track_id INNER JOIN tags g ON tt.tag_id = g.id'
        query += ' WHERE 1=1'
        if description:
            query += ' AND t.description LIKE ?'
            params.append(f'%{description}%')
        if tags:
            query += ' AND g.name IN ({seq})'.format(seq=','.join(['?']*len(tags)))
            params.extend(tags)
        cursor.execute(query, params)
        tracks_rows = cursor.fetchall()
        tracks = []
        for row in tracks_rows:
            track = {
                'id': row[0],
                'name': row[1],
                'description': row[2],
                'url': row[3],
                'tags': self.get_tags_for_track(row[0])
            }
            tracks.append(track)
        return tracks

    def get_tags_for_track(self, track_id):
        cursor = self.conn.cursor()
        cursor.execute('''
            SELECT g.name FROM tags g
            INNER JOIN track_tags tt ON g.id = tt.tag_id
            WHERE tt.track_id = ?
        ''', (track_id,))
        tags = [row[0] for row in cursor.fetchall()]
        return tags
```


requirements.txt
```txt
Flask
```


static/app.js
```js
document.addEventListener('DOMContentLoaded', function () {
    const tracksContainer = document.getElementById('tracks-container');
    const addTrackButton = document.getElementById('add-track-button');
    const addTrackDialog = document.getElementById('add-track-dialog');
    const closeAddDialog = document.getElementById('close-add-dialog');
    const addTrackForm = document.getElementById('add-track-form');
    const tagsContainer = document.getElementById('tags-container');
    const addTagButton = document.getElementById('add-tag-button');
    const audioPlayer = document.getElementById('audio-player');
    const audioControl = document.getElementById('audio-control');
    const trackTitle = document.getElementById('track-title');
    const togglePlayerButton = document.getElementById('toggle-player-button');
    const descriptionDialog = document.getElementById('description-dialog');
    const closeDescriptionDialog = document.getElementById('close-description-dialog');
    const descriptionTitle = document.getElementById('description-title');
    const descriptionText = document.getElementById('description-text');
    const copyLinkButton = document.getElementById('copy-link-button');

    let currentPage = 1;
    let totalTracks = 0;

    // Загрузка треков
    function loadTracks(page = 1) {
        fetch(`/tracks?page=${page}`)
            .then(response => response.json())
            .then(data => {
                totalTracks = data.total;
                tracksContainer.innerHTML = '';
                data.tracks.forEach(track => {
                    const trackTile = document.createElement('div');
                    trackTile.className = 'track-tile';

                    const title = document.createElement('h3');
                    title.textContent = track.name;

                    const playButton = document.createElement('button');
                    playButton.textContent = '►';
                    playButton.addEventListener('click', () => {
                        playTrack(track);
                    });

                    const copyButton = document.createElement('button');
                    copyButton.textContent = 'Скопировать ссылку';
                    copyButton.addEventListener('click', () => {
                        navigator.clipboard.writeText(track.url);
                        alert('Ссылка скопирована в буфер обмена!');
                    });

                    const descriptionButton = document.createElement('button');
                    descriptionButton.textContent = 'Описание';
                    descriptionButton.addEventListener('click', () => {
                        showDescription(track);
                    });

                    trackTile.appendChild(title);
                    trackTile.appendChild(playButton);
                    trackTile.appendChild(copyButton);
                    trackTile.appendChild(descriptionButton);

                    tracksContainer.appendChild(trackTile);
                });
            });
    }

    function playTrack(track) {
        audioControl.src = track.url;
        trackTitle.textContent = track.name;
        audioControl.play();
        maximizePlayer();
    }

    function maximizePlayer() {
        audioPlayer.classList.remove('minimized');
        audioPlayer.classList.add('maximized');
        togglePlayerButton.classList.add('rotated');
    }

    function minimizePlayer() {
        audioPlayer.classList.add('minimized');
        audioPlayer.classList.remove('maximized');
        togglePlayerButton.classList.remove('rotated');
    }

    function showDescription(track) {
        descriptionTitle.textContent = track.name;
        descriptionText.textContent = track.description || 'Нет описания.';
        descriptionDialog.style.display = 'block';
    }

    // Управление диалогом добавления трека
    addTrackButton.addEventListener('click', () => {
        addTrackDialog.style.display = 'block';
    });

    closeAddDialog.addEventListener('click', () => {
        addTrackDialog.style.display = 'none';
    });

    window.addEventListener('click', (event) => {
        if (event.target === addTrackDialog) {
            addTrackDialog.style.display = 'none';
        }
        if (event.target === descriptionDialog) {
            descriptionDialog.style.display = 'none';
        }
    });

    // Добавление тегов
    addTagButton.addEventListener('click', () => {
        const tagItem = document.createElement('div');
        tagItem.className = 'tag-item';

        const tagInput = document.createElement('input');
        tagInput.type = 'text';

        const removeButton = document.createElement('button');
        removeButton.textContent = 'Удалить';
        removeButton.addEventListener('click', () => {
            tagsContainer.removeChild(tagItem);
        });

        tagItem.appendChild(tagInput);
        tagItem.appendChild(removeButton);
        tagsContainer.appendChild(tagItem);
    });

    // Отправка формы добавления трека
    addTrackForm.addEventListener('submit', (event) => {
        event.preventDefault();
        const name = document.getElementById('track-name').value;
        const description = document.getElementById('track-description').value;
        const url = document.getElementById('track-url').value;

        const tags = [];
        const tagInputs = tagsContainer.querySelectorAll('input');
        tagInputs.forEach(input => {
            if (input.value.trim()) {
                tags.push(input.value.trim());
            }
        });

        fetch('/tracks', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ name, description, url, tags })
        })
            .then(response => response.json())
            .then(data => {
                if (data.error) {
                    alert('Ошибка: ' + data.error);
                } else {
                    alert('Трек успешно добавлен!');
                    addTrackDialog.style.display = 'none';
                    addTrackForm.reset();
                    tagsContainer.innerHTML = '';
                    loadTracks();
                }
            });
    });

    // Управление проигрывателем
    togglePlayerButton.addEventListener('click', () => {
        if (audioPlayer.classList.contains('minimized')) {
            maximizePlayer();
        } else {
            minimizePlayer();
        }
    });

    // Закрытие диалога описания
    closeDescriptionDialog.addEventListener('click', () => {
        descriptionDialog.style.display = 'none';
    });

    // Копирование ссылки из проигрывателя
    copyLinkButton.addEventListener('click', () => {
        const src = audioControl.src;
        if (src) {
            navigator.clipboard.writeText(src);
            alert('Ссылка скопирована в буфер обмена!');
        } else {
            alert('Нет ссылки для копирования.');
        }
    });

    // Загрузка треков при загрузке страницы
    loadTracks();
});
```


static/styles.css
```css
body {
    margin: 0;
    font-family: Arial, sans-serif;
    background-color: #ffffff;
    color: #333;
}

header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px;
}

header h1 {
    margin: 0;
    font-size: 24px;
}

#add-track-button {
    padding: 10px 20px;
    font-size: 16px;
}

main {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    padding: 10px;
}

.track-tile {
    border: 1px solid #ddd;
    border-radius: 8px;
    width: 200px;
    margin: 10px;
    padding: 10px;
    text-align: center;
}

.track-tile h3 {
    margin: 0 0 10px 0;
}

.track-tile button {
    margin: 5px;
    padding: 5px 10px;
}

#audio-player {
    position: fixed;
    bottom: 0;
    width: 100%;
    background-color: #f9f9f9;
    border-top: 1px solid #ddd;
    transition: max-height 0.3s ease;
    overflow: hidden;
}

#audio-player.minimized {
    max-height: 40px;
}

#audio-player.maximized {
    max-height: 200px;
}

#player-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px;
}

#now-playing {
    font-size: 16px;
}

#toggle-player-button {
    font-size: 16px;
    transform: rotate(0deg);
    transition: transform 0.3s ease;
}

#toggle-player-button.rotated {
    transform: rotate(180deg);
}

.modal {
    display: none;
    position: fixed;
    z-index: 1000;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: auto;
    background-color: rgba(0,0,0,0.4);
}

.modal-content {
    background-color: #fff;
    margin: 50px auto;
    padding: 20px;
    border-radius: 8px;
    max-width: 500px;
}

.close-button {
    float: right;
    font-size: 28px;
    cursor: pointer;
}

form label {
    display: block;
    margin: 10px 0 5px 0;
}

form input[type="text"],
form input[type="url"],
form textarea {
    width: 100%;
    padding: 8px;
    box-sizing: border-box;
}

#tags-container {
    display: flex;
    flex-direction: column;
}

.tag-item {
    display: flex;
    align-items: center;
    margin-bottom: 5px;
}

.tag-item input {
    flex: 1;
}

.tag-item button {
    margin-left: 5px;
}

@media (max-width: 600px) {
    .track-tile {
        width: 100%;
        margin: 5px 0;
    }

    header h1 {
        font-size: 20px;
    }

    #add-track-button {
        padding: 8px 16px;
        font-size: 14px;
    }
}
```


templates/index.html
```html
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>Музыкальный Сервис</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- Стили -->
    <link rel="stylesheet" href="{{ url_for('send_static', path='styles.css') }}">
</head>
<body>

<!-- Верхняя панель -->
<header>
    <h1>Музыкальный Сервис</h1>
    <button id="add-track-button">Добавить Трек</button>
</header>

<!-- Список треков -->
<main id="tracks-container"></main>

<!-- Проигрыватель -->
<div id="audio-player" class="minimized">
    <div id="player-header">
        <span id="now-playing">Сейчас играет: <span id="track-title"></span></span>
        <button id="copy-link-button">Скопировать ссылку</button>
        <button id="toggle-player-button">▼</button>
    </div>
    <audio id="audio-control" controls></audio>
</div>

<!-- Диалоговое окно добавления трека -->
<div id="add-track-dialog" class="modal">
    <div class="modal-content">
        <span class="close-button" id="close-add-dialog">&times;</span>
        <h2>Добавить Новый Трек</h2>
        <form id="add-track-form">
            <label for="track-name">Название Трека:</label>
            <input type="text" id="track-name" name="name" required>

            <label for="track-description">Описание:</label>
            <textarea id="track-description" name="description"></textarea>

            <label>Теги:</label>
            <div id="tags-container">
                <!-- Контейнер тегов -->
            </div>
            <button type="button" id="add-tag-button">+</button>

            <label for="track-url">Ссылка на Трек:</label>
            <input type="url" id="track-url" name="url" required>

            <button type="submit">Добавить</button>
        </form>
    </div>
</div>

<!-- Диалоговое окно описания -->
<div id="description-dialog" class="modal">
    <div class="modal-content">
        <span class="close-button" id="close-description-dialog">&times;</span>
        <h2 id="description-title"></h2>
        <p id="description-text"></p>
    </div>
</div>

<!-- Скрипты -->
<script src="{{ url_for('send_static', path='app.js') }}"></script>

</body>
</html>
```


Сделай так, чтобы можно было удалять треки.
Также, сделай так, чтобы при добавлении трека, ссылка на трек, которую мы вставляем, использовалась, чтобы скачать его в бд и после уже воспроизводить его на сайте оттуда(копироваться должна все та же ссылка, это менять не надо)