Compare commits
2 Commits
5e124645c0
...
52d01f1bcd
| Author | SHA1 | Date | |
|---|---|---|---|
| 52d01f1bcd | |||
| 607836b280 |
-57
@@ -1,57 +0,0 @@
|
||||
FROM python:3.13-slim
|
||||
|
||||
# Install FFmpeg and dependencies required for gosu installation
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ffmpeg \
|
||||
ca-certificates \
|
||||
wget \
|
||||
gnupg \
|
||||
dirmngr \
|
||||
gnupg-agent && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install gosu (lightweight tool for dropping privileges)
|
||||
RUN set -eux; \
|
||||
dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
|
||||
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.17/gosu-$dpkgArch"; \
|
||||
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/1.17/gosu-$dpkgArch.asc"; \
|
||||
export GNUPGHOME="$(mktemp -d)"; \
|
||||
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
|
||||
gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
|
||||
gpgconf --kill all; \
|
||||
rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
|
||||
chmod +x /usr/local/bin/gosu; \
|
||||
gosu --version
|
||||
|
||||
# Set Python environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package metadata and source code
|
||||
COPY setup.py pyproject.toml README.md ./
|
||||
COPY audio_splitter/ ./audio_splitter/
|
||||
|
||||
# Install the package
|
||||
RUN pip install --no-cache-dir .
|
||||
|
||||
# Create a non-root user with UID 1000
|
||||
RUN addgroup --system --gid 1000 appgroup && \
|
||||
adduser --system --uid 1000 --ingroup appgroup appuser
|
||||
|
||||
# Change ownership of /app to the container user (so it can write there if needed)
|
||||
RUN chown -R appuser:appgroup /app
|
||||
|
||||
# Copy the entrypoint script
|
||||
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
# Set the entrypoint
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||
|
||||
# Default command (shows help if no arguments provided)
|
||||
CMD ["--help"]
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Check if we are running as root (default)
|
||||
if [ "$(id -u)" = "0" ]; then
|
||||
# If /data is a directory, change its ownership to the container user
|
||||
if [ -d "/data" ]; then
|
||||
echo "Setting ownership of /data to appuser:appgroup"
|
||||
chown -R appuser:appgroup /data
|
||||
fi
|
||||
|
||||
# Drop privileges and run audio_splitter with the provided arguments
|
||||
exec gosu appuser audio_splitter "$@"
|
||||
else
|
||||
# If not root, just run audio_splitter directly
|
||||
# It's not supposed to be called
|
||||
exec audio_splitter "$@"
|
||||
fi
|
||||
@@ -1,4 +1,3 @@
|
||||
"""API route handlers."""
|
||||
|
||||
from . import upload, split, status, download
|
||||
# websocket is imported directly in main.py to avoid circular import
|
||||
from . import upload, split, split_file, status, download, formats, info
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
"""Endpoint for splitting with a tracklist file upload."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, UploadFile, File, Form, HTTPException, BackgroundTasks
|
||||
|
||||
from backend.config import settings
|
||||
from backend.services.task_manager import task_manager
|
||||
from backend.services.splitter import run_split_task
|
||||
from backend.models.request import TracklistEntry
|
||||
from backend.models.response import SplitResponse, TaskStatus
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["split"])
|
||||
|
||||
# Import CLI tracklist parsing functions
|
||||
from audio_splitter.tracklist import parse_format, read_tracklist
|
||||
|
||||
|
||||
@router.post("/split-file", response_model=SplitResponse)
|
||||
async def split_from_file(
|
||||
background_tasks: BackgroundTasks,
|
||||
task_id: str = Form(...),
|
||||
tracklist_file: UploadFile = File(...),
|
||||
options: str = Form("{}"),
|
||||
tracklist_format: str = Form("%ts %tn - %an"),
|
||||
):
|
||||
"""
|
||||
Start a splitting task using a tracklist file.
|
||||
|
||||
This endpoint accepts a plain text tracklist file (like the CLI does)
|
||||
and parses it using the same logic.
|
||||
|
||||
Args:
|
||||
task_id: Task ID from upload.
|
||||
tracklist_file: Tracklist file (text/plain).
|
||||
options: JSON string of all CLI options.
|
||||
tracklist_format: Format string for parsing (default: "%ts %tn - %an").
|
||||
|
||||
Returns:
|
||||
SplitResponse with task_id and status.
|
||||
"""
|
||||
# Validate task exists
|
||||
if not task_manager.has_task(task_id):
|
||||
raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
|
||||
|
||||
# Check if task is already processing
|
||||
status = task_manager.get_status(task_id)
|
||||
if status and status["status"] in (TaskStatus.PROCESSING, TaskStatus.DONE):
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail=f"Task {task_id} is already {status['status']}"
|
||||
)
|
||||
|
||||
# Validate file type
|
||||
if not tracklist_file.filename.endswith(('.txt', '.text')):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Tracklist file must be a text file (.txt or .text)"
|
||||
)
|
||||
|
||||
# Read and parse the tracklist file
|
||||
try:
|
||||
content = await tracklist_file.read()
|
||||
text = content.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Tracklist file must be UTF-8 encoded"
|
||||
)
|
||||
|
||||
if not text.strip():
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Tracklist file is empty"
|
||||
)
|
||||
|
||||
# Parse the tracklist using CLI logic
|
||||
try:
|
||||
# Parse the format string into tokens
|
||||
tokens = parse_format(tracklist_format)
|
||||
|
||||
# Write the content to a temporary file (read_tracklist expects a file path)
|
||||
temp_tracklist_path = settings.temp_dir / task_id / "tracklist.txt"
|
||||
temp_tracklist_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temp_tracklist_path.write_text(text, encoding='utf-8')
|
||||
|
||||
# Parse the tracklist file using CLI logic
|
||||
tracklist_dicts = read_tracklist(str(temp_tracklist_path), tokens)
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Failed to parse tracklist: {str(e)}"
|
||||
)
|
||||
|
||||
if not tracklist_dicts:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="No valid tracks found in tracklist file"
|
||||
)
|
||||
|
||||
# Convert dicts to TracklistEntry objects (same as JSON endpoint)
|
||||
try:
|
||||
tracklist_entries = [TracklistEntry(**entry) for entry in tracklist_dicts]
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Invalid tracklist data: {str(e)}"
|
||||
)
|
||||
|
||||
# Parse options JSON
|
||||
try:
|
||||
options_dict = json.loads(options)
|
||||
except json.JSONDecodeError:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Invalid JSON in options field"
|
||||
)
|
||||
|
||||
# Update task status
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
status=TaskStatus.PROCESSING,
|
||||
progress=0,
|
||||
message="Preparing to split..."
|
||||
)
|
||||
|
||||
# Start background task
|
||||
background_tasks.add_task(
|
||||
run_split_task,
|
||||
task_id,
|
||||
tracklist_entries, # now TracklistEntry objects
|
||||
options_dict
|
||||
)
|
||||
|
||||
return SplitResponse(
|
||||
task_id=task_id,
|
||||
status=TaskStatus.PROCESSING
|
||||
)
|
||||
+4
-3
@@ -5,7 +5,7 @@ from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from backend.config import settings
|
||||
from backend.api import upload, split, status, download, websocket, formats, info
|
||||
from backend.api import upload, split, split_file, status, download, websocket, formats, info
|
||||
from backend.services import progress_publisher
|
||||
|
||||
app = FastAPI(
|
||||
@@ -33,11 +33,12 @@ async def startup_event():
|
||||
# Include routers
|
||||
app.include_router(upload.router)
|
||||
app.include_router(split.router)
|
||||
app.include_router(split_file.router) # NEW
|
||||
app.include_router(status.router)
|
||||
app.include_router(download.router)
|
||||
app.include_router(websocket.router)
|
||||
app.include_router(formats.router) # new
|
||||
app.include_router(info.router) # new
|
||||
app.include_router(formats.router)
|
||||
app.include_router(info.router)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
|
||||
@@ -40,8 +40,10 @@ def run_split_task(task_id: str, tracklist: List[TracklistEntry], options: Dict[
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
# Build args namespace from options.
|
||||
# IMPORTANT: format is None if not specified -> core will auto-detect.
|
||||
args = SimpleNamespace(
|
||||
format=options.get("format", "mp3"),
|
||||
format=options.get("format"),
|
||||
transcode_to=options.get("transcode_to", None),
|
||||
drop_video=options.get("drop_video", False),
|
||||
drop_subs=options.get("drop_subs", False),
|
||||
@@ -70,13 +72,10 @@ def run_split_task(task_id: str, tracklist: List[TracklistEntry], options: Dict[
|
||||
task_id, progress=10, message="Starting split..."
|
||||
)
|
||||
|
||||
# Run the split
|
||||
split_audio(str(input_path), str(output_dir), tracks, args)
|
||||
|
||||
# Get output files
|
||||
output_files = FileManager.get_output_files(task_id)
|
||||
|
||||
# Final status update
|
||||
task_manager.update_task_with_progress(
|
||||
task_id,
|
||||
progress=100,
|
||||
@@ -84,7 +83,7 @@ def run_split_task(task_id: str, tracklist: List[TracklistEntry], options: Dict[
|
||||
status=TaskStatus.DONE
|
||||
)
|
||||
|
||||
# Add tracks to the task state
|
||||
# Add tracks to the task state (for download and status queries)
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
tracks=output_files
|
||||
|
||||
Reference in New Issue
Block a user