Update download from search

This commit is contained in:
2025-11-07 14:44:31 +11:00
parent 51fbc8b7eb
commit 036cb513f8
4 changed files with 92 additions and 23 deletions
+74 -4
View File
@@ -18,7 +18,7 @@ router = APIRouter()
logger = logging.getLogger(__name__)
async def process_download_job(job_id: int, db: AsyncSession):
async def process_download_job(job_id: int, db: AsyncSession, direct_url: str = None):
"""Background task to process a download job"""
try:
# Get job
@@ -27,6 +27,69 @@ async def process_download_job(job_id: int, db: AsyncSession):
if not job:
return
# Check if this is a direct URL download (skip search)
if direct_url:
logger.info(f"Direct URL download job {job_id}: url='{direct_url}', song_name='{job.song_name}'")
# Skip search, go directly to download
job.status = "downloading"
await db.commit()
# Format output filename
output_filename = job.song_name # Already formatted as "Artist - Title"
# Download the music
success, file_path, error = await music_downloader.download_music(
direct_url,
output_filename
)
if success and file_path:
# Extract metadata
metadata = await music_downloader.get_music_metadata(file_path)
# Determine source type
source_type = "youtube" if music_downloader.is_youtube_url(direct_url) else \
"bilibili" if music_downloader.is_bilibili_url(direct_url) else "other"
# Get relative path and file format
relative_path = os.path.relpath(file_path, settings.MUSIC_DIR)
file_extension = os.path.splitext(file_path)[1][1:]
# Create database record
db_music = Music(
title=metadata.get("title", job.song_name or "Unknown"),
artist=metadata.get("artist", "Unknown"),
album=metadata.get("album", ""),
duration=metadata.get("duration", 0),
file_path=relative_path,
file_size=os.path.getsize(file_path),
file_format=file_extension,
file_location=file_path,
file_exists=True,
source_url=direct_url,
source_type=source_type,
thumbnail=metadata.get("thumbnail")
)
db.add(db_music)
await db.commit()
await db.refresh(db_music)
job.status = "completed"
job.music_id = db_music.id
await db.commit()
logger.info(f"Direct download job {job_id} completed: music_id={db_music.id}")
else:
job.status = "failed"
job.error_message = error or "Download failed"
await db.commit()
logger.error(f"Direct download job {job_id} failed: {error}")
return
# Original auto-download flow (with search)
# Update status to searching
job.status = "searching"
await db.commit()
@@ -159,10 +222,16 @@ async def create_download_job(
# Format song_name to include artist if provided
# This ensures better search results
song_name = request.song_name
if request.artist:
# Format as "Artist - Song" for better search accuracy
if request.artist and not request.direct_url:
# Format as "Artist - Song" for better search accuracy (only if not direct URL)
song_name = f"{request.artist} - {request.song_name}"
elif request.artist and request.direct_url:
# For direct URL, still format the output filename
song_name = f"{request.artist} - {request.song_name}"
if request.direct_url:
logger.info(f"Creating direct download job: url='{request.direct_url}', filename='{song_name}'")
else:
logger.info(f"Creating auto-download job: song_name='{song_name}' (original: '{request.song_name}', artist: '{request.artist}')")
# Create job
@@ -176,7 +245,8 @@ async def create_download_job(
await db.refresh(job)
# Start processing in background
background_tasks.add_task(process_download_job, job.id, db)
# Pass direct_url if provided to skip search
background_tasks.add_task(process_download_job, job.id, db, request.direct_url)
return job
+1
View File
@@ -148,6 +148,7 @@ class MusicDetailInfo(BaseModel):
class DownloadJobCreate(BaseModel):
song_name: str
artist: Optional[str] = None # Artist name to improve search accuracy
direct_url: Optional[str] = None # Direct URL to download (skips search)
class DownloadJobResponse(BaseModel):
+2 -2
View File
@@ -100,8 +100,8 @@ export const settingsApi = {
// Auto Download API
export const autoDownloadApi = {
createJob: (songName: string, artist?: string) =>
api.post('/auto-download/job', { song_name: songName, artist }),
createJob: (songName: string, artist?: string, directUrl?: string) =>
api.post('/auto-download/job', { song_name: songName, artist, direct_url: directUrl }),
getJobs: (status?: string) =>
api.get('/auto-download/jobs', { params: { status } }),
getJob: (jobId: number) =>
+12 -14
View File
@@ -1,6 +1,6 @@
import { useState } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { searchApi, downloadApi, musicApi } from '@/api/client'
import { useQuery, useMutation } from '@tanstack/react-query'
import { searchApi, autoDownloadApi, musicApi } from '@/api/client'
import { Music } from '@/types'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
@@ -14,7 +14,6 @@ interface SearchPageProps {
export default function SearchPage({ onPlayMusic }: SearchPageProps) {
const [query, setQuery] = useState('')
const [searchQuery, setSearchQuery] = useState('')
const queryClient = useQueryClient()
const { data: localResults, isLoading: isLoadingLocal } = useQuery({
queryKey: ['music', 'search', searchQuery],
@@ -37,14 +36,13 @@ export default function SearchPage({ onPlayMusic }: SearchPageProps) {
})
const downloadMutation = useMutation({
mutationFn: ({ url, title, thumbnail, artist }: { url: string; title: string; thumbnail?: string; artist?: string }) =>
downloadApi.downloadMusic({ url, title, thumbnail, artist }),
mutationFn: ({ title, artist, url }: { title: string; artist?: string; url?: string }) =>
autoDownloadApi.createJob(title, artist, url),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['download-status'] })
toast.success('Download started! Check Download Center for progress.')
toast.success('Download job created! Check Download Center for progress.')
},
onError: () => {
toast.error('Download failed')
toast.error('Failed to create download job')
},
})
@@ -53,13 +51,13 @@ export default function SearchPage({ onPlayMusic }: SearchPageProps) {
setSearchQuery(query)
}
const handleDownload = (url: string, title: string, thumbnail?: string, artist?: string) => {
downloadMutation.mutate({ url, title, thumbnail, artist })
const handleDownload = (title: string, artist?: string, url?: string) => {
downloadMutation.mutate({ title, artist, url })
}
const handlePlayAndDownload = async (url: string, title: string, thumbnail?: string, artist?: string) => {
// Start download
downloadMutation.mutate({ url, title, thumbnail, artist })
// Start download job with direct URL
downloadMutation.mutate({ title, artist, url })
// Create temporary music object for streaming playback
const streamUrl = `/api/stream?url=${encodeURIComponent(url)}`
@@ -187,7 +185,7 @@ export default function SearchPage({ onPlayMusic }: SearchPageProps) {
<Button
size="icon"
variant="outline"
onClick={() => handleDownload(result.url, result.title, result.thumbnail, result.artist)}
onClick={() => handleDownload(result.title, result.artist, result.url)}
disabled={downloadMutation.isPending}
>
<Download className="h-5 w-5" />
@@ -231,7 +229,7 @@ export default function SearchPage({ onPlayMusic }: SearchPageProps) {
<Button
size="icon"
variant="outline"
onClick={() => handleDownload(result.url, result.title, result.thumbnail, result.artist)}
onClick={() => handleDownload(result.title, result.artist, result.url)}
disabled={downloadMutation.isPending}
>
<Download className="h-5 w-5" />