Merge pull request 'feat(core): single source of truth for default values added' (#6) from default_args into dev
Reviewed-on: #6
This commit was merged in pull request #6.
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
"""Default values for all configurable options shared between CLI, web backend, and frontend."""
|
||||
|
||||
# Output container format (used when user doesn't specify one; auto-detection overrides this)
|
||||
DEFAULT_FORMAT = "mp3"
|
||||
|
||||
# Filename template
|
||||
DEFAULT_OUTPUT_TEMPLATE = "%an-%tn.%ext"
|
||||
|
||||
# Character replacement
|
||||
DEFAULT_REPLACEMENT_CHAR = "_"
|
||||
DEFAULT_BAD_CHARS = r'!@#№$;:%^&?*(){}[]\/<>+=~`\' '
|
||||
|
||||
# Tracklist parsing
|
||||
DEFAULT_TRACKLIST_FORMAT = "%ts %tn - %an"
|
||||
|
||||
# Metadata defaults
|
||||
DEFAULT_ALBUM = None
|
||||
DEFAULT_COMMENT = None
|
||||
DEFAULT_COMMENT_STREAM = None
|
||||
DEFAULT_COMMENT_SEPARATOR = "; "
|
||||
DEFAULT_NO_COMMENT = False
|
||||
DEFAULT_MERGE_COMMENTS = False
|
||||
|
||||
# Stream handling
|
||||
DEFAULT_DROP_VIDEO = False
|
||||
DEFAULT_DROP_SUBS = False
|
||||
|
||||
# Filename/export options
|
||||
DEFAULT_NUMBER_TRACKS = False
|
||||
DEFAULT_REPLACE_BAD_CHARS = True
|
||||
DEFAULT_SKIP_EXISTING = False
|
||||
DEFAULT_DELETE_ORIGINAL = False
|
||||
|
||||
# Transcoding (None means copy codec)
|
||||
DEFAULT_TRANSCODE_TO = None
|
||||
+50
-116
@@ -1,133 +1,67 @@
|
||||
"""Command‑line interface and entry point."""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import argparse
|
||||
|
||||
from .constants import DEFAULT_BAD_CHARS
|
||||
from .tracklist import read_tracklist, parse_format
|
||||
from .defaults import (
|
||||
DEFAULT_FORMAT,
|
||||
DEFAULT_OUTPUT_TEMPLATE,
|
||||
DEFAULT_REPLACEMENT_CHAR,
|
||||
DEFAULT_BAD_CHARS,
|
||||
DEFAULT_TRACKLIST_FORMAT,
|
||||
DEFAULT_ALBUM,
|
||||
DEFAULT_COMMENT,
|
||||
DEFAULT_COMMENT_STREAM,
|
||||
DEFAULT_COMMENT_SEPARATOR,
|
||||
DEFAULT_NO_COMMENT,
|
||||
DEFAULT_MERGE_COMMENTS,
|
||||
DEFAULT_DROP_VIDEO,
|
||||
DEFAULT_DROP_SUBS,
|
||||
DEFAULT_NUMBER_TRACKS,
|
||||
DEFAULT_REPLACE_BAD_CHARS,
|
||||
DEFAULT_SKIP_EXISTING,
|
||||
DEFAULT_DELETE_ORIGINAL,
|
||||
DEFAULT_TRANSCODE_TO,
|
||||
)
|
||||
from .core import split_audio
|
||||
|
||||
from .tracklist import read_tracklist, parse_format
|
||||
|
||||
def main():
|
||||
"""Parse arguments and start the splitting process."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Split an audio file into tracks using a tracklist.",
|
||||
epilog="Tracklist format: mm:ss track_name - author_name"
|
||||
)
|
||||
|
||||
# Positional arguments.
|
||||
parser.add_argument('input_file', help='Input audio/video file')
|
||||
parser = argparse.ArgumentParser(description="Split audio file using a tracklist.")
|
||||
parser.add_argument('input_file', help='Input audio file')
|
||||
parser.add_argument('tracklist_file', help='Tracklist file')
|
||||
|
||||
# Optional arguments.
|
||||
parser.add_argument(
|
||||
'--output-dir', '-o',
|
||||
help='Output directory for split tracks (default: <input_basename>_splits)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--format',
|
||||
help='Output container format (e.g., mp3, m4a, mkv, mp4, ogg, opus)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--transcode-to',
|
||||
help='Re-encode audio to this codec (e.g., libmp3lame, aac, libopus)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--drop-video',
|
||||
action='store_true',
|
||||
help='Remove video streams from output'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--drop-subs',
|
||||
action='store_true',
|
||||
help='Remove subtitle streams from output'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--number-tracks',
|
||||
action='store_true',
|
||||
help='Prepend track number to output filenames (convenience; use %%num in template for full control)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--replace-bad-chars',
|
||||
action='store_true',
|
||||
help='Replace problematic characters in filenames (default: off)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--replacement-char',
|
||||
default='_',
|
||||
help='Character used as replacement (default: "_")'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--bad-chars',
|
||||
default=DEFAULT_BAD_CHARS,
|
||||
help='String of characters to replace (default includes space and single quote)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--skip-existing',
|
||||
action='store_true',
|
||||
help='Skip extraction if output file already exists (default: overwrite)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--tracklist-format',
|
||||
default='%ts %tn - %an',
|
||||
help='Format of each line in the tracklist using placeholders: '
|
||||
'%%ts (timestamp), %%tn (track name), %%an (author), %%al (album), '
|
||||
'%%date (date/year), %%ext (file extension). '
|
||||
'Default: "%%ts %%tn - %%an"'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--output-template',
|
||||
default='%an-%tn.%ext',
|
||||
help='Template for output filenames using placeholders: '
|
||||
'%%tn (track name), %%an (author), %%al (album), '
|
||||
'%%date (date/year), %%ext (file extension), %%num (track number). '
|
||||
'Default: "%%an-%%tn.%%ext"'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
help='Parse and display the tracklist without splitting any files'
|
||||
)
|
||||
# Output options
|
||||
parser.add_argument('--format', default=DEFAULT_FORMAT, help=f"Output container format (default: {DEFAULT_FORMAT})")
|
||||
parser.add_argument('--transcode-to', default=DEFAULT_TRANSCODE_TO, help="Audio codec to transcode to (default: copy)")
|
||||
parser.add_argument('--drop-video', action='store_true', default=DEFAULT_DROP_VIDEO, help="Drop video streams")
|
||||
parser.add_argument('--drop-subs', action='store_true', default=DEFAULT_DROP_SUBS, help="Drop subtitle streams")
|
||||
|
||||
# Metadata options.
|
||||
parser.add_argument(
|
||||
'--album',
|
||||
help='Set album name in output metadata (overrides parsed %%al and original album)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--comment',
|
||||
help='Explicit comment text (overrides all other comment settings)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--no-comment',
|
||||
action='store_true',
|
||||
help='Explicitly ignore any comment (no comment written)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--comment-stream',
|
||||
type=int,
|
||||
default=None,
|
||||
help='Select comment from a specific stream index (0‑based). Default: first stream with a comment.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--merge-comments',
|
||||
action='store_true',
|
||||
help='Merge all comments from all streams into one (separated by --comment-separator)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--comment-separator',
|
||||
default='; ',
|
||||
help='Separator used when merging comments (default: "; ")'
|
||||
)
|
||||
# Filename options
|
||||
parser.add_argument('--number-tracks', action='store_true', default=DEFAULT_NUMBER_TRACKS, help="Prepend track numbers")
|
||||
parser.add_argument('--output-template', default=DEFAULT_OUTPUT_TEMPLATE, help=f"Output filename template (default: {DEFAULT_OUTPUT_TEMPLATE})")
|
||||
parser.add_argument('--replace-bad-chars', action='store_true', default=DEFAULT_REPLACE_BAD_CHARS, help="Replace bad characters")
|
||||
parser.add_argument('--replacement-char', default=DEFAULT_REPLACEMENT_CHAR, help=f"Replacement character (default: {DEFAULT_REPLACEMENT_CHAR})")
|
||||
parser.add_argument('--bad-chars', default=DEFAULT_BAD_CHARS, help=f"Bad characters to replace (default: {DEFAULT_BAD_CHARS})")
|
||||
parser.add_argument('--skip-existing', action='store_true', default=DEFAULT_SKIP_EXISTING, help="Skip existing output files")
|
||||
|
||||
# NEW: Delete original after successful split.
|
||||
parser.add_argument(
|
||||
'--delete-original',
|
||||
action='store_true',
|
||||
help='Delete the original input file after successful splitting (default: keep)'
|
||||
)
|
||||
# Metadata options
|
||||
parser.add_argument('--album', default=DEFAULT_ALBUM, help="Album name")
|
||||
parser.add_argument('--comment', default=DEFAULT_COMMENT, help="Comment")
|
||||
parser.add_argument('--no-comment', action='store_true', default=DEFAULT_NO_COMMENT, help="Ignore comment")
|
||||
parser.add_argument('--comment-stream', type=int, default=DEFAULT_COMMENT_STREAM, help="Comment stream index")
|
||||
parser.add_argument('--merge-comments', action='store_true', default=DEFAULT_MERGE_COMMENTS, help="Merge all comments")
|
||||
parser.add_argument('--comment-separator', default=DEFAULT_COMMENT_SEPARATOR, help=f"Separator for merged comments (default: {DEFAULT_COMMENT_SEPARATOR})")
|
||||
|
||||
# Tracklist format
|
||||
parser.add_argument('--tracklist-format', default=DEFAULT_TRACKLIST_FORMAT, help=f"Tracklist format (default: {DEFAULT_TRACKLIST_FORMAT})")
|
||||
|
||||
# Other
|
||||
parser.add_argument('--delete-original', action='store_true', default=DEFAULT_DELETE_ORIGINAL, help="Delete original file after split")
|
||||
parser.add_argument('--dry-run', action='store_true', help="Parse and display tracklist without splitting")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
services:
|
||||
backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: web/backend/Dockerfile
|
||||
volumes:
|
||||
# Bind mount for source code (hot-reload for Python)
|
||||
- ./web/backend:/app/backend
|
||||
- ./audio_splitter:/app/audio_splitter
|
||||
- ./setup.py:/app/setup.py
|
||||
- ./pyproject.toml:/app/pyproject.toml
|
||||
# Development data directory (overrides production)
|
||||
- "./dev_data:/tmp/audio_splitter_web"
|
||||
environment:
|
||||
- DEBUG=1
|
||||
- PYTHONUNBUFFERED=1
|
||||
ports:
|
||||
- "8000:8000"
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: ./web/frontend
|
||||
# Use the same Dockerfile, but override CMD for development
|
||||
dockerfile: Dockerfile
|
||||
command: ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
|
||||
volumes:
|
||||
# Bind mount for source code (hot-reload for Vite)
|
||||
- ./web/frontend:/app
|
||||
- node_modules:/app/node_modules
|
||||
environment:
|
||||
- BACKEND_URL=http://backend:8000
|
||||
- VITE_BACKEND_URL=http://backend:8000
|
||||
ports:
|
||||
- "5173:5173"
|
||||
|
||||
volumes:
|
||||
node_modules:
|
||||
+10
-5
@@ -1,19 +1,24 @@
|
||||
services:
|
||||
backend:
|
||||
image: git.vmn.su/max/audio_splitter_backend:latest
|
||||
build:
|
||||
context: .
|
||||
dockerfile: web/backend/Dockerfile
|
||||
#image: max/audio_splitter_backend:latest
|
||||
ports:
|
||||
- "${BACKEND_PORT:-8000}:8000"
|
||||
volumes:
|
||||
# Bind mount for persistent data storage.
|
||||
# Set BACKEND_DATA_DIR in .env to point to your data directory.
|
||||
- "${BACKEND_DATA_DIR:-/var/lib/audio_splitter_data}:/tmp/audio_splitter_web"
|
||||
#- "${BACKEND_DATA_DIR:-/var/lib/audio_splitter_data}:/tmp/audio_splitter_web"
|
||||
- "/tmp/test:/tmp/audio_splitter_web"
|
||||
environment:
|
||||
- PYTHONUNBUFFERED=1
|
||||
- DEBUG=${DEBUG:-0}
|
||||
restart: unless-stopped
|
||||
|
||||
frontend:
|
||||
image: git.vmn.su/max/audio_splitter_frontend:latest
|
||||
build:
|
||||
context: .
|
||||
dockerfile: web/frontend/Dockerfile
|
||||
#image: max/audio_splitter_frontend:latest
|
||||
ports:
|
||||
- "${FRONTEND_PORT:-5173}:80"
|
||||
depends_on:
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate TypeScript constants from audio_splitter/defaults.py.
|
||||
"""
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Get the directory where this script is located
|
||||
SCRIPT_DIR = Path(__file__).parent.absolute()
|
||||
|
||||
# The defaults.py is in the same directory as the script
|
||||
DEFAULTS_PATH = SCRIPT_DIR / "defaults.py"
|
||||
|
||||
# The frontend source is at SCRIPT_DIR.parent (i.e., /app)
|
||||
# The generated file should be at /app/src/constants/generated.ts
|
||||
FRONTEND_ROOT = SCRIPT_DIR.parent
|
||||
OUTPUT_PATH = FRONTEND_ROOT / "src" / "constants" / "generated.ts"
|
||||
|
||||
|
||||
def load_defaults_module():
|
||||
spec = importlib.util.spec_from_file_location("defaults", DEFAULTS_PATH)
|
||||
if spec is None:
|
||||
raise RuntimeError(f"Could not load spec for {DEFAULTS_PATH}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def format_value(value):
|
||||
"""Convert Python value to TypeScript literal."""
|
||||
if value is None:
|
||||
return "null"
|
||||
if isinstance(value, bool):
|
||||
return str(value).lower()
|
||||
if isinstance(value, str):
|
||||
# Use json.dumps to produce a properly escaped string literal
|
||||
return json.dumps(value)
|
||||
if isinstance(value, (int, float)):
|
||||
return str(value)
|
||||
if isinstance(value, list):
|
||||
return f"[{', '.join(format_value(v) for v in value)}]"
|
||||
return repr(value)
|
||||
|
||||
|
||||
def main():
|
||||
print(f"Generating TypeScript defaults from {DEFAULTS_PATH}")
|
||||
defaults = load_defaults_module()
|
||||
|
||||
constants = {name: value for name, value in vars(defaults).items() if name.startswith("DEFAULT_")}
|
||||
|
||||
if not constants:
|
||||
print("No DEFAULT_* constants found.")
|
||||
sys.exit(1)
|
||||
|
||||
lines = [
|
||||
"// ============================================================================",
|
||||
"// GENERATED FILE – DO NOT EDIT MANUALLY.",
|
||||
"// This file is generated from audio_splitter/defaults.py.",
|
||||
"// Run `npm run generate` or `python scripts/generate_ts_defaults.py` to update.",
|
||||
"// ============================================================================",
|
||||
"",
|
||||
]
|
||||
|
||||
for name, value in sorted(constants.items()):
|
||||
ts_value = format_value(value)
|
||||
lines.append(f"export const {name} = {ts_value};")
|
||||
|
||||
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUTPUT_PATH.write_text("\n".join(lines) + "\n")
|
||||
|
||||
print(f"✅ Generated {OUTPUT_PATH}")
|
||||
print(f" {len(constants)} constants exported.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Any
|
||||
import time
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Dict, List, Any
|
||||
|
||||
from backend.config import settings
|
||||
from backend.services.task_manager import task_manager
|
||||
@@ -13,12 +13,31 @@ from backend.services.file_manager import FileManager
|
||||
from backend.models.request import TracklistEntry
|
||||
from backend.models.response import TaskStatus
|
||||
|
||||
# Import central defaults
|
||||
from audio_splitter.defaults import (
|
||||
DEFAULT_FORMAT,
|
||||
DEFAULT_OUTPUT_TEMPLATE,
|
||||
DEFAULT_REPLACEMENT_CHAR,
|
||||
DEFAULT_BAD_CHARS,
|
||||
DEFAULT_ALBUM,
|
||||
DEFAULT_COMMENT,
|
||||
DEFAULT_NO_COMMENT,
|
||||
DEFAULT_COMMENT_STREAM,
|
||||
DEFAULT_MERGE_COMMENTS,
|
||||
DEFAULT_COMMENT_SEPARATOR,
|
||||
DEFAULT_DROP_VIDEO,
|
||||
DEFAULT_DROP_SUBS,
|
||||
DEFAULT_NUMBER_TRACKS,
|
||||
DEFAULT_REPLACE_BAD_CHARS,
|
||||
DEFAULT_SKIP_EXISTING,
|
||||
DEFAULT_DELETE_ORIGINAL,
|
||||
DEFAULT_TRANSCODE_TO,
|
||||
)
|
||||
|
||||
|
||||
def run_split_task(task_id: str, tracklist: List[TracklistEntry], options: Dict[str, Any]) -> None:
|
||||
try:
|
||||
task_manager.update_task_with_progress(
|
||||
task_id, progress=5, message="Initializing..."
|
||||
)
|
||||
task_manager.update_task_with_progress(task_id, progress=5, message="Initializing...")
|
||||
|
||||
input_path = FileManager.get_input_path(task_id)
|
||||
if not input_path or not input_path.exists():
|
||||
@@ -26,6 +45,7 @@ def run_split_task(task_id: str, tracklist: List[TracklistEntry], options: Dict[
|
||||
|
||||
output_dir = FileManager.ensure_output_dir(task_id)
|
||||
|
||||
# Convert tracklist to dicts (CLI format)
|
||||
tracks = []
|
||||
for entry in tracklist:
|
||||
track_dict = {
|
||||
@@ -38,28 +58,25 @@ def run_split_task(task_id: str, tracklist: List[TracklistEntry], options: Dict[
|
||||
}
|
||||
tracks.append(track_dict)
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
# Build args namespace from options.
|
||||
# IMPORTANT: format is None if not specified -> core will auto-detect.
|
||||
# Build args namespace using defaults where options not provided
|
||||
args = SimpleNamespace(
|
||||
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),
|
||||
number_tracks=options.get("number_tracks", False),
|
||||
replace_bad_chars=options.get("replace_bad_chars", False),
|
||||
replacement_char=options.get("replacement_char", "_"),
|
||||
bad_chars=options.get("bad_chars", r'!@#№$;:%^&?*(){}[]\/<>+=~`\' '),
|
||||
skip_existing=options.get("skip_existing", False),
|
||||
output_template=options.get("output_template", "%an-%tn.%ext"),
|
||||
album=options.get("album", None),
|
||||
comment=options.get("comment", None),
|
||||
no_comment=options.get("no_comment", False),
|
||||
comment_stream=options.get("comment_stream", None),
|
||||
merge_comments=options.get("merge_comments", False),
|
||||
comment_separator=options.get("comment_separator", "; "),
|
||||
delete_original=False,
|
||||
format=options.get("format", DEFAULT_FORMAT),
|
||||
transcode_to=options.get("transcode_to", DEFAULT_TRANSCODE_TO),
|
||||
drop_video=options.get("drop_video", DEFAULT_DROP_VIDEO),
|
||||
drop_subs=options.get("drop_subs", DEFAULT_DROP_SUBS),
|
||||
number_tracks=options.get("number_tracks", DEFAULT_NUMBER_TRACKS),
|
||||
replace_bad_chars=options.get("replace_bad_chars", DEFAULT_REPLACE_BAD_CHARS),
|
||||
replacement_char=options.get("replacement_char", DEFAULT_REPLACEMENT_CHAR),
|
||||
bad_chars=options.get("bad_chars", DEFAULT_BAD_CHARS),
|
||||
skip_existing=options.get("skip_existing", DEFAULT_SKIP_EXISTING),
|
||||
output_template=options.get("output_template", DEFAULT_OUTPUT_TEMPLATE),
|
||||
album=options.get("album", DEFAULT_ALBUM),
|
||||
comment=options.get("comment", DEFAULT_COMMENT),
|
||||
no_comment=options.get("no_comment", DEFAULT_NO_COMMENT),
|
||||
comment_stream=options.get("comment_stream", DEFAULT_COMMENT_STREAM),
|
||||
merge_comments=options.get("merge_comments", DEFAULT_MERGE_COMMENTS),
|
||||
comment_separator=options.get("comment_separator", DEFAULT_COMMENT_SEPARATOR),
|
||||
delete_original=DEFAULT_DELETE_ORIGINAL, # never delete in web
|
||||
)
|
||||
|
||||
project_root = Path(__file__).parent.parent.parent.parent
|
||||
@@ -68,9 +85,7 @@ def run_split_task(task_id: str, tracklist: List[TracklistEntry], options: Dict[
|
||||
|
||||
from audio_splitter.core import split_audio
|
||||
|
||||
task_manager.update_task_with_progress(
|
||||
task_id, progress=10, message="Starting split..."
|
||||
)
|
||||
task_manager.update_task_with_progress(task_id, progress=10, message="Starting split...")
|
||||
|
||||
split_audio(str(input_path), str(output_dir), tracks, args)
|
||||
|
||||
@@ -83,13 +98,11 @@ def run_split_task(task_id: str, tracklist: List[TracklistEntry], options: Dict[
|
||||
status=TaskStatus.DONE
|
||||
)
|
||||
|
||||
# Add tracks to the task state (for download and status queries)
|
||||
task_manager.update_task(
|
||||
task_id,
|
||||
tracks=output_files
|
||||
)
|
||||
|
||||
# Give WebSocket time to send the final message
|
||||
time.sleep(0.5)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
+27
-12
@@ -1,27 +1,42 @@
|
||||
# Stage 1: Build
|
||||
# web/frontend/Dockerfile
|
||||
# Build context must be the project root.
|
||||
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
# Install Python for the generation script
|
||||
RUN apk add --no-cache python3 py3-pip
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY package.json package-lock.json* ./
|
||||
# Copy frontend package files and install dependencies
|
||||
COPY web/frontend/package.json web/frontend/package-lock.json* ./
|
||||
RUN npm install
|
||||
|
||||
# Copy the application code and build
|
||||
COPY . .
|
||||
# Copy the frontend source code
|
||||
COPY web/frontend/ .
|
||||
|
||||
# Copy the Python defaults and the generation script into the expected location
|
||||
# The generate script in package.json expects ../../scripts/generate_ts_defaults.py
|
||||
# So we must place it at /app/../../scripts/ which is /scripts/
|
||||
# But we can't COPY to a parent directory. Instead, we'll copy to /app/scripts/
|
||||
# and adjust the package.json script to use ./scripts/generate_ts_defaults.py
|
||||
# Actually, the simplest fix is to copy to /app/scripts/ and then adjust package.json.
|
||||
#
|
||||
# Let's use a different approach: copy to /app/scripts/ and update the generate script.
|
||||
COPY audio_splitter/defaults.py /app/scripts/defaults.py
|
||||
COPY scripts/generate_ts_defaults.py /app/scripts/generate_ts_defaults.py
|
||||
|
||||
# Run the generation script
|
||||
RUN python /app/scripts/generate_ts_defaults.py
|
||||
|
||||
# Build the frontend
|
||||
RUN npm run build
|
||||
|
||||
# Stage 2: Production (nginx)
|
||||
FROM nginx:alpine
|
||||
|
||||
# Copy built assets from builder
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
COPY web/frontend/nginx/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Copy nginx configuration
|
||||
COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Expose the port
|
||||
EXPOSE 80
|
||||
|
||||
# Start nginx
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"generate": "python scripts/generate_ts_defaults.py",
|
||||
"predev": "npm run generate",
|
||||
"prebuild": "npm run generate",
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
|
||||
@@ -1,24 +1,44 @@
|
||||
// web/frontend/src/stores/optionsStore.ts
|
||||
import { create } from 'zustand'
|
||||
import { SplitOptions } from '../types'
|
||||
import {
|
||||
DEFAULT_FORMAT,
|
||||
DEFAULT_OUTPUT_TEMPLATE,
|
||||
DEFAULT_REPLACEMENT_CHAR,
|
||||
DEFAULT_BAD_CHARS,
|
||||
DEFAULT_ALBUM,
|
||||
DEFAULT_COMMENT,
|
||||
DEFAULT_NO_COMMENT,
|
||||
DEFAULT_COMMENT_STREAM,
|
||||
DEFAULT_MERGE_COMMENTS,
|
||||
DEFAULT_COMMENT_SEPARATOR,
|
||||
DEFAULT_DROP_VIDEO,
|
||||
DEFAULT_DROP_SUBS,
|
||||
DEFAULT_NUMBER_TRACKS,
|
||||
DEFAULT_REPLACE_BAD_CHARS,
|
||||
DEFAULT_SKIP_EXISTING,
|
||||
DEFAULT_TRANSCODE_TO,
|
||||
DEFAULT_TRACKLIST_FORMAT,
|
||||
} from '../constants/generated'
|
||||
|
||||
const DEFAULT_OPTIONS: SplitOptions = {
|
||||
format: 'mp3',
|
||||
transcode_to: '',
|
||||
drop_video: false,
|
||||
drop_subs: false,
|
||||
number_tracks: false,
|
||||
replace_bad_chars: false,
|
||||
replacement_char: '_',
|
||||
bad_chars: '!@#№$;:%^&?*(){}[]\\/<>+=~`\' ',
|
||||
skip_existing: false,
|
||||
output_template: '%an-%tn.%ext',
|
||||
album: '',
|
||||
comment: '',
|
||||
no_comment: false,
|
||||
comment_stream: null,
|
||||
merge_comments: false,
|
||||
comment_separator: '; ',
|
||||
tracklist_format: '%ts %tn - %an', // NEW
|
||||
format: DEFAULT_FORMAT,
|
||||
transcode_to: DEFAULT_TRANSCODE_TO ?? '',
|
||||
drop_video: DEFAULT_DROP_VIDEO,
|
||||
drop_subs: DEFAULT_DROP_SUBS,
|
||||
number_tracks: DEFAULT_NUMBER_TRACKS,
|
||||
replace_bad_chars: DEFAULT_REPLACE_BAD_CHARS,
|
||||
replacement_char: DEFAULT_REPLACEMENT_CHAR,
|
||||
bad_chars: DEFAULT_BAD_CHARS,
|
||||
skip_existing: DEFAULT_SKIP_EXISTING,
|
||||
output_template: DEFAULT_OUTPUT_TEMPLATE,
|
||||
album: DEFAULT_ALBUM ?? '',
|
||||
comment: DEFAULT_COMMENT ?? '',
|
||||
no_comment: DEFAULT_NO_COMMENT,
|
||||
comment_stream: DEFAULT_COMMENT_STREAM,
|
||||
merge_comments: DEFAULT_MERGE_COMMENTS,
|
||||
comment_separator: DEFAULT_COMMENT_SEPARATOR,
|
||||
tracklist_format: DEFAULT_TRACKLIST_FORMAT,
|
||||
}
|
||||
|
||||
interface OptionsState {
|
||||
|
||||
Reference in New Issue
Block a user