diff --git a/.env.example b/.env.example index e65c022..cb418df 100644 --- a/.env.example +++ b/.env.example @@ -5,7 +5,12 @@ NEXTAUTH_SECRET="replace-with-a-long-random-secret" GOOGLE_CLIENT_ID="your-google-client-id.apps.googleusercontent.com" GOOGLE_CLIENT_SECRET="your-google-client-secret" UPLOAD_DIR="./uploads" -S2YT_EDITION="selfhosted" -NEXT_PUBLIC_GITEA_ISSUES_URL="https://git.atakanozban.com/Songs2YT/songs2yt/issues" -NEXT_PUBLIC_GITEA_URL="https://git.atakanozban.com/Songs2YT/songs2yt" -NEXT_PUBLIC_DOCKER_HUB_URL="https://hub.docker.com/r/atakanozban/songs2yt" +# Self-hosted OSS edition (unlocked features, no Stripe / credits) +S2VID_EDITION="selfhosted" +# Optional legacy alias (also accepted by lib/edition.ts): +# S2YT_EDITION="selfhosted" +# Public docs (default: https://docs.songs2vid.com) +# NEXT_PUBLIC_DOCS_URL="https://docs.songs2vid.com" +NEXT_PUBLIC_GITEA_ISSUES_URL="https://git.atakanozban.com/Songs2VID/songs2vid/issues" +NEXT_PUBLIC_GITEA_URL="https://git.atakanozban.com/Songs2VID/songs2vid" +NEXT_PUBLIC_DOCKER_HUB_URL="https://hub.docker.com/r/atakanozban/songs2vid" diff --git a/.gitignore b/.gitignore index 672c1b2..8c3877b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,5 @@ website/.docusaurus/ .DS_Store basibozuk_cover.jpg bg-video/ -scripts/ _restore/ +tsconfig.tsbuildinfo diff --git a/Dockerfile b/Dockerfile index c08491a..280da81 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ FROM node:22-bookworm-slim AS runner WORKDIR /app ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 -ENV S2YT_EDITION=selfhosted +ENV S2VID_EDITION=selfhosted ENV UPLOAD_DIR=/app/uploads RUN apt-get update && apt-get install -y --no-install-recommends \ openssl ca-certificates ffmpeg \ @@ -36,6 +36,7 @@ COPY --from=builder /app/package-lock.json ./package-lock.json COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/static ./.next/static +COPY --from=builder /app/assets ./assets COPY --from=builder /app/worker ./worker COPY --from=builder /app/lib ./lib COPY --from=builder /app/tsconfig.json ./tsconfig.json diff --git a/README.md b/README.md index 9778477..2b12983 100644 --- a/README.md +++ b/README.md @@ -8,82 +8,79 @@
- Turn your cover art + audio files into YouTube videos automatically. No manual rendering BS.
- The ultimate life-saver for creators who waste hours opening Premiere/Vegas just to upload a single track.
+ Turn your cover art + audio files into YouTube videos automatically.
+ Art-track layouts, blur backgrounds, typography, watermark studio, and per-track covers — unlocked for self-hosting.
- {children}
-
- );
-}
-
-export default async function ApiDocsPage() {
- const session = await getServerSession(authOptions);
- if (!session?.user?.email) redirect("/");
-
- const user = await prisma.user.findUnique({
- where: { email: session.user.email },
- include: { youtubeConnection: true },
- });
- if (!user) redirect("/");
-
- const isPro = hasProFeatures(user.plan);
-
- return (
- - Programmatic uploads and batch jobs for Pro subscribers. Generate your API key in{" "} - - Settings - - . -
- {!isPro && ( -
- API access requires Pro.
- Send your API key on every request. Keys start with s2yt_live_.
-
- Default is 60 requests per minute per account. Approved rate-limit extensions increase - that ceiling. Check live usage and request an extension in{" "} - - Settings → API access - - . Video quota limits from your Pro plan still apply to job creation. -
-
- Recommended for most jobs (especially 5+ audio files):{" "}
- two-step: upload each file with POST /api/v1/upload, then
- create the job with POST /api/v1/jobs (JSON).
-
- One-shot batch{" "}
- (POST /api/v1/jobs/batch) is for small packs only, typically
- 1 cover + up to a few audio files. Large multipart bodies often fail with{" "}
- failed to parse body as FormData. Prefer two-step for albums
- or long tracklists.
-
Content-Type manually for multipart; the client must include the boundary.audio (type File).Returns endpoint list and requirements (no auth).
-
- Response includes path,{" "}
- filename, and{" "}
- size. Upload the image once, then each audio file. Keep the{" "}
- path values for the next step.
-
- Use the exact path strings returned by upload. Add one{" "}
- items[] entry per track.
-
- List existing playlists, create a new one, or create one inline when starting a job.
- Pass playlistId in item metadata / batch{" "}
- defaults, or use{" "}
- createPlaylist to make a playlist and attach all
- videos to it. Privacy may be public,{" "}
- unlisted, or{" "}
- private. If playlist permission was just added,
- sign out and sign in again for youtube.force-ssl.
-
- Upload one cover image and a few audio files in a single multipart request. Not recommended for - large batches: use two-step instead if you see FormData parse errors. -
-
- Optional metadata JSON supports{" "}
- defaults applied to every item and per-item overrides in{" "}
- items. Item order should match the order of{" "}
- audio files.
-
- Videos processed -
-- {selfHosted ? ( - quota.used - ) : ( - <> - {quota.remaining} - - {" "} - of {quota.limit} videos - - > - )} -
-- {quota.used} used - {quota.bonusQuota > 0 ? ` · +${quota.bonusQuota} bonus` : ""} -
- )} -- {user.plan === "PREMIUM" - ? `Monthly quota resets on ${quota.resetsIn}` - : `Quota resets in ${quota.resetsIn}`} -
- > - )} - {selfHosted && ( -- Self-hosted edition: no video or API quotas. +
+ Videos processed
- )} +{quota.used}
++ Self-hosted edition: no video credits or quotas. +
-
- Songs2YT is an automation tool that converts your audio and image files into - high-quality videos and uploads them directly to YouTube. + Self-hosted automation that turns your audio and cover art into YouTube videos — + including art-track layouts, typography, and watermark studio. No quotas or paywalls.
+ + Documentation + + {" · "} + + Hosted product + +
- Use the REST API to upload files and create batch video jobs programmatically. Pro plan - only. + Use the REST API to upload files and create batch video jobs programmatically.
API rate limit
-- Resets in {rateLimit.resetsInSeconds}s -
+Resets in {rateLimit.resetsInSeconds}s
{rateLimit.used} / {rateLimit.limit} requests used this minute - {rateLimit.bonus ? ( - (includes +{rateLimit.bonus} bonus) - ) : null}
-- {rateLimit.remaining} requests remaining · window {rateLimit.windowSeconds}s + Self-hosted: API rate limits are effectively unlimited.
- -- Extension requests this year: {extensionUsage.used} / {extensionUsage.limit} -
- -Send Authorization: Bearer YOUR_API_KEY on every - request. For many audio files, upload each file then call{" "} - /api/v1/jobs (avoid large one-shot batches).{" "} - + request.{" "} + Full API docs
diff --git a/components/BenefitsSection.tsx b/components/BenefitsSection.tsx index ad4a1b1..0fc6e84 100644 --- a/components/BenefitsSection.tsx +++ b/components/BenefitsSection.tsx @@ -77,7 +77,7 @@ function GearsIcon({ className }: { className?: string }) { const BENEFITS = [ { title: "No editing required", - desc: "Skip complex video editors. Just upload an image and audio, and Songs2YT handles the rest.", + desc: "Skip complex video editors. Just upload an image and audio, and Songs2VID handles the rest.", Icon: FilmStripIcon, }, { @@ -86,13 +86,13 @@ const BENEFITS = [ Icon: YouTubeIcon, }, { - title: "Free tier included", - desc: "Start creating with 14 videos every 12 hours at up to 720p. No credit card needed.", + title: "Self-hosted, unlocked", + desc: "No video quotas or paywalls. Art-track layouts, typography, watermarks, and the REST API are all available.", Icon: CoinsIcon, }, { title: "YouTube playlists", - desc: "Independent Artist (Pro) can create YouTube playlists and add every upload from the dashboard or API.", + desc: "Create YouTube playlists and add every upload from the dashboard or API.", Icon: PlaylistIcon, }, ] as const; diff --git a/components/Footer.tsx b/components/Footer.tsx index 29c4657..24215dd 100644 --- a/components/Footer.tsx +++ b/components/Footer.tsx @@ -2,10 +2,13 @@ import Link from "next/link"; import type { ReactNode } from "react"; import { Logo } from "@/components/Logo"; import { + API_DOCS_URL, DOCKER_HUB_URL, + DOCS_URL, GITEA_ISSUES_URL, GITEA_URL, SUPPORT_EMAIL, + WEBSITE_URL, } from "@/lib/plans"; function GiteaIcon({ className }: { className?: string }) { @@ -88,6 +91,7 @@ function LegalLink({ } const STATUS_URL = "https://status.atakanozban.com/status/2"; +const DOCS_INTRO_URL = `${DOCS_URL}/docs/intro`; export function Footer() { const year = new Date().getFullYear(); @@ -129,12 +133,19 @@ export function Footer() { Product -+ {title.trim() || "Track title"} +
++ {artist.trim() || "Artist"} +
+Composition
+Watermark
+Uploading font…
+ )} + {fontError &&{fontError}
} ++ Preview: The quick brown fox jumps over the lazy dog +
+ )} +Uploading logo…
+ )} + {logoError &&{logoError}
} +Watermark position
+- Extension requests this year:{" "} - - {usage.used} / {usage.limit} - - {usage.remaining > 0 ? ( - · {usage.remaining} remaining - ) : ( - · limit reached - )} -
-- Request history -
-{req.message}
} -ID: {req.id}
-- Pro users may request up to 5 manual quota resets or extensions per calendar year. See our{" "} - - Terms of Service - - . -
-
- Add uploaded videos to a YouTube playlist with{" "}
-
{label}
- {label === "Infrastructure" ? ( -- {watermarkNote} -
- )} -- Free for getting started, Pro for creators, or Enterprise for teams. -
-- *Pro is a monthly subscription with included quota (extensions via support). Self-hosted - open-source deployments have no quotas. -
-- Technical support is strictly reserved for Managed Cloud and paid Professional Setup - agreements; independent self-hosted deployments are community-supported. -
-+ Shared cover for all tracks. Optionally override per audio below. +
+ {item.itemImagePath ? `Using ${item.itemImageName}` : `Uploading ${item.itemImageName}…`} +
+ )} + +- Watermark is optional on the free plan - turn it off anytime for a clean video. -
-
-