Compare commits
2 Commits
main
..
52d01f1bcd
| Author | SHA1 | Date | |
|---|---|---|---|
| 52d01f1bcd | |||
| 607836b280 |
@@ -1,126 +0,0 @@
|
|||||||
name: "Bug Report"
|
|
||||||
description: "Report a bug or unexpected behavior to help us improve"
|
|
||||||
title: "[BUG]: "
|
|
||||||
labels: ["bug"]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
## ⚠️ Before You Begin
|
|
||||||
Please ensure you have:
|
|
||||||
- [ ] Searched existing issues to avoid duplicates
|
|
||||||
- [ ] Confirmed this is a bug, not a question or configuration problem
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: description
|
|
||||||
attributes:
|
|
||||||
label: "📋 Description"
|
|
||||||
description: "Provide a clear and concise description of the bug"
|
|
||||||
placeholder: "What happened? What did you expect to happen instead?"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: reproduction
|
|
||||||
attributes:
|
|
||||||
label: "🔁 Steps to Reproduce"
|
|
||||||
description: "Step-by-step instructions to reproduce the issue"
|
|
||||||
placeholder: |
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: expected
|
|
||||||
attributes:
|
|
||||||
label: "✅ Expected Behavior"
|
|
||||||
description: "What you expected to happen"
|
|
||||||
placeholder: "A clear description of what should happen..."
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: actual
|
|
||||||
attributes:
|
|
||||||
label: "❌ Actual Behavior"
|
|
||||||
description: "What actually happened"
|
|
||||||
placeholder: "Include error messages, stack traces, or unexpected outcomes..."
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: logs
|
|
||||||
attributes:
|
|
||||||
label: "📄 Logs / Screenshots"
|
|
||||||
description: |
|
|
||||||
Provide relevant logs, error messages, or screenshots.
|
|
||||||
For logs, please use a pastebin and share the URL.
|
|
||||||
**Remember to remove any sensitive information (API keys, passwords, etc.).**[reference:13]
|
|
||||||
placeholder: "Paste logs here or provide a Gist URL..."
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
---
|
|
||||||
## 🖥️ Environment Details
|
|
||||||
Please fill out the relevant information below.
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: version
|
|
||||||
attributes:
|
|
||||||
label: "📦 Audio Splitter Version"
|
|
||||||
description: "The version you are using (or commit reference)"
|
|
||||||
placeholder: "e.g., v1.21.7"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: os
|
|
||||||
attributes:
|
|
||||||
label: "💻 Operating System"
|
|
||||||
description: "Your OS and version"
|
|
||||||
placeholder: "e.g., Ubuntu 22.04, macOS Sonoma 14.5, Windows"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: browser
|
|
||||||
attributes:
|
|
||||||
label: "🌍 Browser (if applicable)"
|
|
||||||
description: "Browser name and version"
|
|
||||||
placeholder: "e.g., Chrome 120, Firefox 121, Safari 17"
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: additional
|
|
||||||
attributes:
|
|
||||||
label: "📎 Additional Context"
|
|
||||||
description: "Any other information that might be relevant"
|
|
||||||
placeholder: |
|
|
||||||
- Database type and version (e.g., PostgreSQL 15, SQLite)
|
|
||||||
- Reverse proxy/CDN in use (e.g., Nginx, Cloudflare)[reference:15]
|
|
||||||
- Any custom configuration
|
|
||||||
- Related issues or PRs
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: checklist
|
|
||||||
attributes:
|
|
||||||
label: "✅ Submission Checklist"
|
|
||||||
description: "Please confirm the following before submitting"
|
|
||||||
options:
|
|
||||||
- label: "I have searched for existing issues (open and closed) that report the same problem"
|
|
||||||
required: true
|
|
||||||
- label: "I am using the latest stable release of Audio Splitter"
|
|
||||||
required: true
|
|
||||||
- label: "I have provided clear steps to reproduce the issue"
|
|
||||||
required: true
|
|
||||||
- label: "I have included relevant logs or error messages (with sensitive info removed)"
|
|
||||||
required: false
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
name: "Feature Request"
|
|
||||||
description: "Suggest a new feature or enhancement for this project"
|
|
||||||
title: "[FEATURE]: "
|
|
||||||
labels: ["enhancement"]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
## ⚠️ Before You Begin
|
|
||||||
Please ensure you have:
|
|
||||||
- [ ] Searched existing issues (open and closed) to avoid duplicates
|
|
||||||
- [ ] Reviewed the [project documentation](https://git.vmn.su/max/audio_splitter/wiki) for existing functionality
|
|
||||||
- [ ] Confirmed this is a feature request, not a bug or configuration question
|
|
||||||
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: problem
|
|
||||||
attributes:
|
|
||||||
label: "🎯 Problem Statement"
|
|
||||||
description: "What problem does this feature solve? What can't you do today?"
|
|
||||||
placeholder: |
|
|
||||||
I'm always frustrated when...
|
|
||||||
Currently, it's hard/impossible to...
|
|
||||||
This feature would help me because...
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: solution
|
|
||||||
attributes:
|
|
||||||
label: "💡 Proposed Solution"
|
|
||||||
description: "Describe the solution you'd like to see"
|
|
||||||
placeholder: |
|
|
||||||
A clear description of what you want to happen.
|
|
||||||
If you have a specific API, UI, or implementation in mind, describe it here.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: alternatives
|
|
||||||
attributes:
|
|
||||||
label: "🔄 Alternatives Considered"
|
|
||||||
description: "What alternative solutions or workarounds have you considered?"
|
|
||||||
placeholder: |
|
|
||||||
- Alternative A: ...
|
|
||||||
- Workaround B: ...
|
|
||||||
- Why these don't fully solve the problem
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: usecase
|
|
||||||
attributes:
|
|
||||||
label: "👤 Use Case / Persona"
|
|
||||||
description: "Who benefits from this feature and in what context?"
|
|
||||||
placeholder: |
|
|
||||||
As a [type of user], I want to [do something] so that [I achieve some benefit].
|
|
||||||
Example: "As a project maintainer, I want to bulk-close issues by label so that I can clean up stale tickets faster."
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: screenshots
|
|
||||||
attributes:
|
|
||||||
label: "📸 Screenshots"
|
|
||||||
description: "If you can, provide screenshots or diagrams of the proposed feature"
|
|
||||||
placeholder: "Drag and drop images or paste links..."
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
---
|
|
||||||
## 🔧 Implementation Details (Optional)
|
|
||||||
The following sections are for contributors who want to implement this feature.
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: technical
|
|
||||||
attributes:
|
|
||||||
label: "⚙️ Technical Approach"
|
|
||||||
description: "If you have ideas about how to implement this, share them here"
|
|
||||||
placeholder: |
|
|
||||||
- Which components/modules would be affected?
|
|
||||||
- Any database schema changes?
|
|
||||||
- API design considerations?
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: acceptance
|
|
||||||
attributes:
|
|
||||||
label: "✅ Acceptance Criteria"
|
|
||||||
description: "What does 'done' look like for this feature?"
|
|
||||||
placeholder: |
|
|
||||||
- [ ] Feature works in the UI
|
|
||||||
- [ ] API endpoints are documented
|
|
||||||
- [ ] Tests are added
|
|
||||||
- [ ] Documentation is updated
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: dropdown
|
|
||||||
id: contribution
|
|
||||||
attributes:
|
|
||||||
label: "🤝 Are you willing to contribute this feature?"
|
|
||||||
description: "Knowing if you can help implement this helps us prioritize"
|
|
||||||
options:
|
|
||||||
- "Yes, I can contribute"
|
|
||||||
- "Yes, but I need guidance"
|
|
||||||
- "No, but I can test/review"
|
|
||||||
- "No, I'm just requesting"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: checklist
|
|
||||||
attributes:
|
|
||||||
label: "✅ Submission Checklist"
|
|
||||||
description: "Please confirm the following before submitting"
|
|
||||||
options:
|
|
||||||
- label: "I have searched for existing issues (open and closed) that request the same feature"
|
|
||||||
required: true
|
|
||||||
- label: "I have explained the problem this feature solves"
|
|
||||||
required: true
|
|
||||||
- label: "I have described a concrete solution or direction"
|
|
||||||
required: true
|
|
||||||
- label: "I have considered alternatives and explained why they fall short"
|
|
||||||
required: false
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
name: "Refactor Request"
|
|
||||||
description: "Propose a code restructuring or cleanup with no behavior change"
|
|
||||||
title: "[REFACTOR]: "
|
|
||||||
labels: ["refactoring"]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
## ⚠️ Before You Begin
|
|
||||||
Please ensure you have:
|
|
||||||
- [ ] Searched existing issues (open and closed) to avoid duplicates
|
|
||||||
- [ ] Reviewed the [project documentation](https://git.vmn.su/max/audio_splitter/wiki) for architecture guidelines
|
|
||||||
- [ ] Confirmed this is a refactor (code restructuring with NO behavior change), not a bug fix or new feature
|
|
||||||
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: problem
|
|
||||||
attributes:
|
|
||||||
label: "🎯 Problem Statement"
|
|
||||||
description: "What pain points does the current code cause? Why is this refactor needed?"
|
|
||||||
placeholder: |
|
|
||||||
The current implementation is difficult to maintain because...
|
|
||||||
This code has accumulated technical debt due to...
|
|
||||||
I'm constantly frustrated when working with this module because...
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: scope
|
|
||||||
attributes:
|
|
||||||
label: "📂 Scope"
|
|
||||||
description: "Which files, modules, or components need refactoring? Be specific."
|
|
||||||
placeholder: |
|
|
||||||
- File paths: `src/auth/`, `internal/handler/`
|
|
||||||
- Component: `UserService` class
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: current-state
|
|
||||||
attributes:
|
|
||||||
label: "🔍 Current State"
|
|
||||||
description: "Describe the current code structure, its issues, and why it's problematic"
|
|
||||||
placeholder: |
|
|
||||||
- The function `processUser()` is 500+ lines long
|
|
||||||
- Duplicated logic across 3 different files
|
|
||||||
- Mixed concerns (business logic + HTTP handling)
|
|
||||||
- Poor test coverage makes changes risky
|
|
||||||
- Naming is unclear and inconsistent
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: proposed-changes
|
|
||||||
attributes:
|
|
||||||
label: "💡 Proposed Solution"
|
|
||||||
description: "What should the code look like after the refactor?"
|
|
||||||
placeholder: |
|
|
||||||
- Extract `processUser()` into smaller, focused functions
|
|
||||||
- Consolidate duplicated logic into a shared utility
|
|
||||||
- Separate business logic from HTTP handlers
|
|
||||||
- Introduce interfaces for better testability
|
|
||||||
- Rename variables and functions for clarity
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: constraints
|
|
||||||
attributes:
|
|
||||||
label: "🚫 Constraints"
|
|
||||||
description: "What must NOT change? Public APIs, behavior, backward compatibility, performance characteristics?"
|
|
||||||
placeholder: |
|
|
||||||
- All public APIs must remain identical
|
|
||||||
- Database schema must not change
|
|
||||||
- External behavior must be identical
|
|
||||||
- Response formats must stay the same
|
|
||||||
- Performance must not degrade
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: benefits
|
|
||||||
attributes:
|
|
||||||
label: "📈 Expected Benefits"
|
|
||||||
description: "What improvements will this refactor bring?"
|
|
||||||
placeholder: |
|
|
||||||
- Improved maintainability
|
|
||||||
- Better testability
|
|
||||||
- Reduced code duplication
|
|
||||||
- Clearer separation of concerns
|
|
||||||
- Easier onboarding for new contributors
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: risks
|
|
||||||
attributes:
|
|
||||||
label: "⚠️ Risks & Mitigations"
|
|
||||||
description: "What could go wrong, and how will you mitigate it?"
|
|
||||||
placeholder: |
|
|
||||||
- Risk: Regression bugs
|
|
||||||
Mitigation: Comprehensive test coverage before and after
|
|
||||||
- Risk: Large diff making code review difficult
|
|
||||||
Mitigation: Break into smaller, incremental PRs
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: testing
|
|
||||||
attributes:
|
|
||||||
label: "🧪 Testing Strategy"
|
|
||||||
description: "How will you ensure the refactor doesn't break existing functionality?"
|
|
||||||
placeholder: |
|
|
||||||
- Existing test suite must pass
|
|
||||||
- Add regression tests before refactoring
|
|
||||||
- Run benchmarks to ensure no performance regression
|
|
||||||
- Manual testing of critical paths
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: alternatives
|
|
||||||
attributes:
|
|
||||||
label: "🔄 Alternatives Considered"
|
|
||||||
description: "What other approaches did you consider, and why did you reject them?"
|
|
||||||
placeholder: |
|
|
||||||
- Alternative A: Complete rewrite — rejected because too risky
|
|
||||||
- Alternative B: Gradual deprecation — rejected because too slow
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: dropdown
|
|
||||||
id: contribution
|
|
||||||
attributes:
|
|
||||||
label: "🤝 Are you willing to contribute this refactor?"
|
|
||||||
description: "Knowing if you can help implement this helps us prioritize"
|
|
||||||
options:
|
|
||||||
- "Yes, I can contribute"
|
|
||||||
- "Yes, but I need guidance"
|
|
||||||
- "No, but I can review"
|
|
||||||
- "No, I'm just suggesting"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: checklist
|
|
||||||
attributes:
|
|
||||||
label: "✅ Submission Checklist"
|
|
||||||
description: "Please confirm the following before submitting"
|
|
||||||
options:
|
|
||||||
- label: "I have searched for existing issues (open and closed) that request the same refactor"
|
|
||||||
required: true
|
|
||||||
- label: "I have clearly explained the problem this refactor solves"
|
|
||||||
required: true
|
|
||||||
- label: "I have described the scope of the refactor (specific files/modules)"
|
|
||||||
required: true
|
|
||||||
- label: "I have identified what must NOT change (APIs, behavior, backward compatibility)"
|
|
||||||
required: true
|
|
||||||
- label: "I have described how the refactor will be tested"
|
|
||||||
required: true
|
|
||||||
-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."""
|
"""API route handlers."""
|
||||||
|
|
||||||
from . import upload, split, status, download
|
from . import upload, split, split_file, status, download, formats, info
|
||||||
# websocket is imported directly in main.py to avoid circular import
|
|
||||||
|
|||||||
@@ -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 fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
from backend.config import settings
|
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
|
from backend.services import progress_publisher
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
@@ -33,11 +33,12 @@ async def startup_event():
|
|||||||
# Include routers
|
# Include routers
|
||||||
app.include_router(upload.router)
|
app.include_router(upload.router)
|
||||||
app.include_router(split.router)
|
app.include_router(split.router)
|
||||||
|
app.include_router(split_file.router) # NEW
|
||||||
app.include_router(status.router)
|
app.include_router(status.router)
|
||||||
app.include_router(download.router)
|
app.include_router(download.router)
|
||||||
app.include_router(websocket.router)
|
app.include_router(websocket.router)
|
||||||
app.include_router(formats.router) # new
|
app.include_router(formats.router)
|
||||||
app.include_router(info.router) # new
|
app.include_router(info.router)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
|
|||||||
@@ -40,8 +40,10 @@ def run_split_task(task_id: str, tracklist: List[TracklistEntry], options: Dict[
|
|||||||
|
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
# Build args namespace from options.
|
||||||
|
# IMPORTANT: format is None if not specified -> core will auto-detect.
|
||||||
args = SimpleNamespace(
|
args = SimpleNamespace(
|
||||||
format=options.get("format", "mp3"),
|
format=options.get("format"),
|
||||||
transcode_to=options.get("transcode_to", None),
|
transcode_to=options.get("transcode_to", None),
|
||||||
drop_video=options.get("drop_video", False),
|
drop_video=options.get("drop_video", False),
|
||||||
drop_subs=options.get("drop_subs", 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..."
|
task_id, progress=10, message="Starting split..."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Run the split
|
|
||||||
split_audio(str(input_path), str(output_dir), tracks, args)
|
split_audio(str(input_path), str(output_dir), tracks, args)
|
||||||
|
|
||||||
# Get output files
|
|
||||||
output_files = FileManager.get_output_files(task_id)
|
output_files = FileManager.get_output_files(task_id)
|
||||||
|
|
||||||
# Final status update
|
|
||||||
task_manager.update_task_with_progress(
|
task_manager.update_task_with_progress(
|
||||||
task_id,
|
task_id,
|
||||||
progress=100,
|
progress=100,
|
||||||
@@ -84,7 +83,7 @@ def run_split_task(task_id: str, tracklist: List[TracklistEntry], options: Dict[
|
|||||||
status=TaskStatus.DONE
|
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_manager.update_task(
|
||||||
task_id,
|
task_id,
|
||||||
tracks=output_files
|
tracks=output_files
|
||||||
|
|||||||
Reference in New Issue
Block a user