mirror of
https://github.com/wahyd4/you-music.git
synced 2026-08-08 20:59:47 +10:00
Set proper thumbnail
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
"""Add thumbnail to download_jobs
|
||||
|
||||
Revision ID: dcb7e2f03d2b
|
||||
Revises: 86ec19b8f5ce
|
||||
Create Date: 2025-11-07 20:26:29.893708
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'dcb7e2f03d2b'
|
||||
down_revision: Union[str, Sequence[str], None] = '86ec19b8f5ce'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('download_jobs', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('thumbnail', sa.String(), nullable=True))
|
||||
|
||||
with op.batch_alter_table('music', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_music_created_at'))
|
||||
batch_op.drop_index(batch_op.f('ix_music_updated_at'))
|
||||
|
||||
with op.batch_alter_table('playlists', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_playlists_created_at'))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('playlists', schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f('ix_playlists_created_at'), ['created_at'], unique=False)
|
||||
|
||||
with op.batch_alter_table('music', schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f('ix_music_updated_at'), ['updated_at'], unique=False)
|
||||
batch_op.create_index(batch_op.f('ix_music_created_at'), ['created_at'], unique=False)
|
||||
|
||||
with op.batch_alter_table('download_jobs', schema=None) as batch_op:
|
||||
batch_op.drop_column('thumbnail')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
@@ -69,7 +69,7 @@ async def process_download_job(job_id: int, db: AsyncSession, direct_url: str =
|
||||
file_exists=True,
|
||||
source_url=direct_url,
|
||||
source_type=source_type,
|
||||
thumbnail=metadata.get("thumbnail")
|
||||
thumbnail=metadata.get("thumbnail") or job.thumbnail
|
||||
)
|
||||
|
||||
db.add(db_music)
|
||||
@@ -237,7 +237,8 @@ async def create_download_job(
|
||||
# Create job
|
||||
job = DownloadJob(
|
||||
song_name=song_name,
|
||||
status="pending"
|
||||
status="pending",
|
||||
thumbnail=request.thumbnail
|
||||
)
|
||||
|
||||
db.add(job)
|
||||
|
||||
@@ -109,6 +109,7 @@ class DownloadJob(Base):
|
||||
confirmed = Column(Boolean, default=False) # Whether user confirmed duplicate download
|
||||
is_duplicate = Column(Boolean, default=False) # Whether song already exists
|
||||
duplicate_music_id = Column(Integer, nullable=True) # ID of existing duplicate
|
||||
thumbnail = Column(String, nullable=True) # Thumbnail URL from search results
|
||||
created_at = Column(DateTime, default=datetime.utcnow, index=True)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
|
||||
@@ -149,6 +149,7 @@ 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)
|
||||
thumbnail: Optional[str] = None # Thumbnail URL from search results
|
||||
|
||||
|
||||
class DownloadJobResponse(BaseModel):
|
||||
@@ -164,6 +165,7 @@ class DownloadJobResponse(BaseModel):
|
||||
confirmed: bool
|
||||
is_duplicate: bool
|
||||
duplicate_music_id: Optional[int] = None
|
||||
thumbnail: Optional[str] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
@@ -175,10 +175,20 @@ class MusicDownloader:
|
||||
files = []
|
||||
for ext in ['.mp3', '.m4a', '.opus', '.webm']:
|
||||
if output_name:
|
||||
# Try exact match first
|
||||
pattern = f"{output_name}{ext}"
|
||||
file_path = os.path.join(self.download_path, pattern)
|
||||
if os.path.exists(file_path):
|
||||
return file_path
|
||||
|
||||
# yt-dlp may sanitize differently (e.g., " -> ')
|
||||
# Try fuzzy match: look for files that start with similar name
|
||||
# Extract base name without special chars for comparison
|
||||
base_search = re.sub(r'[^\w\s-]', '', output_name.lower())
|
||||
for file in Path(self.download_path).glob(f"*{ext}"):
|
||||
file_base = re.sub(r'[^\w\s-]', '', file.stem.lower())
|
||||
if file_base == base_search:
|
||||
return str(file)
|
||||
else:
|
||||
# Find most recent file
|
||||
for file in Path(self.download_path).glob(f"*{ext}"):
|
||||
|
||||
+2
-7
@@ -77,11 +77,11 @@ echo ""
|
||||
# 3. Backend Setup
|
||||
# ============================================================================
|
||||
echo "🔧 Setting up Backend..."
|
||||
cd backend
|
||||
|
||||
# Create virtual environment if needed
|
||||
if [ ! -d "backend/.venv" ]; then
|
||||
if [ ! -d ".venv" ]; then
|
||||
echo "Creating Python 3.13 virtual environment..."
|
||||
cd backend
|
||||
uv venv --python 3.13
|
||||
cd ..
|
||||
fi
|
||||
@@ -91,11 +91,6 @@ echo "Installing Python dependencies with uv..."
|
||||
uv sync
|
||||
cd ..
|
||||
|
||||
# Create backend .env if needed
|
||||
if [ ! -f "backend/.env" ]; then
|
||||
echo "Creating backend .env file..."
|
||||
cp backend/.env.example backend/.env 2>/dev/null || echo "Warning: .env.example not found"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✅ Backend setup complete${NC}"
|
||||
echo ""
|
||||
|
||||
@@ -100,8 +100,8 @@ export const settingsApi = {
|
||||
|
||||
// Auto Download API
|
||||
export const autoDownloadApi = {
|
||||
createJob: (songName: string, artist?: string, directUrl?: string) =>
|
||||
api.post('/auto-download/job', { song_name: songName, artist, direct_url: directUrl }),
|
||||
createJob: (songName: string, artist?: string, directUrl?: string, thumbnail?: string) =>
|
||||
api.post('/auto-download/job', { song_name: songName, artist, direct_url: directUrl, thumbnail }),
|
||||
getJobs: (status?: string) =>
|
||||
api.get('/auto-download/jobs', { params: { status } }),
|
||||
getJob: (jobId: number) =>
|
||||
|
||||
@@ -36,8 +36,8 @@ export default function SearchPage({ onPlayMusic }: SearchPageProps) {
|
||||
})
|
||||
|
||||
const downloadMutation = useMutation({
|
||||
mutationFn: ({ title, artist, url }: { title: string; artist?: string; url?: string }) =>
|
||||
autoDownloadApi.createJob(title, artist, url),
|
||||
mutationFn: ({ title, artist, url, thumbnail }: { title: string; artist?: string; url?: string; thumbnail?: string }) =>
|
||||
autoDownloadApi.createJob(title, artist, url, thumbnail),
|
||||
onSuccess: () => {
|
||||
toast.success('Download job created! Check Download Center for progress.')
|
||||
},
|
||||
@@ -51,13 +51,13 @@ export default function SearchPage({ onPlayMusic }: SearchPageProps) {
|
||||
setSearchQuery(query)
|
||||
}
|
||||
|
||||
const handleDownload = (title: string, artist?: string, url?: string) => {
|
||||
downloadMutation.mutate({ title, artist, url })
|
||||
const handleDownload = (title: string, artist?: string, url?: string, thumbnail?: string) => {
|
||||
downloadMutation.mutate({ title, artist, url, thumbnail })
|
||||
}
|
||||
|
||||
const handlePlayAndDownload = async (url: string, title: string, thumbnail?: string, artist?: string) => {
|
||||
// Start download job with direct URL
|
||||
downloadMutation.mutate({ title, artist, url })
|
||||
downloadMutation.mutate({ title, artist, url, thumbnail })
|
||||
|
||||
// Create temporary music object for streaming playback
|
||||
const streamUrl = `/api/stream?url=${encodeURIComponent(url)}`
|
||||
@@ -185,7 +185,7 @@ export default function SearchPage({ onPlayMusic }: SearchPageProps) {
|
||||
<Button
|
||||
size="icon"
|
||||
variant="outline"
|
||||
onClick={() => handleDownload(result.title, result.artist, result.url)}
|
||||
onClick={() => handleDownload(result.title, result.artist, result.url, result.thumbnail)}
|
||||
disabled={downloadMutation.isPending}
|
||||
>
|
||||
<Download className="h-5 w-5" />
|
||||
@@ -229,7 +229,7 @@ export default function SearchPage({ onPlayMusic }: SearchPageProps) {
|
||||
<Button
|
||||
size="icon"
|
||||
variant="outline"
|
||||
onClick={() => handleDownload(result.title, result.artist, result.url)}
|
||||
onClick={() => handleDownload(result.title, result.artist, result.url, result.thumbnail)}
|
||||
disabled={downloadMutation.isPending}
|
||||
>
|
||||
<Download className="h-5 w-5" />
|
||||
|
||||
Reference in New Issue
Block a user