import { Plan } from "@prisma/client"; import { getResolution, RESOLUTIONS } from "./constants"; import { isSelfHostedEdition } from "./edition"; export type PlanLimits = { monthlyQuota: number; maxBatchSize: number; maxResolutionHeight: number; maxFileSizeBytes: number; watermarkOptional: boolean; /** Custom text / PNG logo + position controls (Pro). */ customWatermark: boolean; /** Pair a unique cover image per audio in a batch (Pro). */ perItemImages: boolean; /** Blurred cover background + art-track layout templates (Pro). */ artTrackLayouts: boolean; allowedAudioExtensions: readonly string[]; id3TagSupport: boolean; }; export const PLAN_LIMITS: Record = { FREE: { monthlyQuota: 10, maxBatchSize: 3, maxResolutionHeight: 720, maxFileSizeBytes: 30 * 1024 * 1024, watermarkOptional: true, customWatermark: false, perItemImages: false, artTrackLayouts: false, allowedAudioExtensions: [".mp3"], id3TagSupport: true, }, PREMIUM: { monthlyQuota: 50, maxBatchSize: 5, maxResolutionHeight: 1080, maxFileSizeBytes: 30 * 1024 * 1024, watermarkOptional: true, customWatermark: true, perItemImages: true, artTrackLayouts: true, allowedAudioExtensions: [".mp3", ".wav", ".flac"], id3TagSupport: true, }, }; export const SELFHOSTED_LIMITS: PlanLimits = { monthlyQuota: 1_000_000, maxBatchSize: 100, maxResolutionHeight: Math.max(...RESOLUTIONS.map((r) => r.height)), maxFileSizeBytes: 500 * 1024 * 1024, watermarkOptional: true, customWatermark: true, perItemImages: true, artTrackLayouts: true, allowedAudioExtensions: [".mp3", ".wav", ".flac"], id3TagSupport: true, }; export const SALES_EMAIL = "songs2vid@atakanozban.com"; export const SUPPORT_EMAIL = "songs2vid@atakanozban.com"; /** Pro quota reset / extension requests */ export const QUOTA_REQUEST_EMAIL = "songs2vid@atakanozban.com"; export const UPGRADE_URL = "/#pricing"; export const GITEA_ISSUES_URL = process.env.NEXT_PUBLIC_GITEA_ISSUES_URL ?? "https://git.atakanozban.com/Songs2VID/songs2vid/issues"; export const GITEA_URL = process.env.NEXT_PUBLIC_GITEA_URL ?? "https://git.atakanozban.com/Songs2VID"; export const DOCKER_HUB_URL = process.env.NEXT_PUBLIC_DOCKER_HUB_URL ?? "https://hub.docker.com/r/atakanozban/songs2vid"; /** Docusaurus docs base URL. Local: http://localhost:3001 (npm run docs:dev). */ export function resolveDocsUrl(): string { if (process.env.NEXT_PUBLIC_DOCS_URL?.trim()) { return process.env.NEXT_PUBLIC_DOCS_URL.replace(/\/$/, ""); } const auth = process.env.NEXTAUTH_URL ?? ""; if (/localhost|127\.0\.0\.1/i.test(auth)) { return "http://localhost:3001"; } return "https://docs.songs2vid.com"; } export const DOCS_URL = resolveDocsUrl(); export const API_DOCS_URL = `${DOCS_URL}/docs/api/overview`; export function getPlanLimits(plan: Plan): PlanLimits { if (isSelfHostedEdition()) return SELFHOSTED_LIMITS; return PLAN_LIMITS[plan]; } export function getResolutionsForPlan(plan: Plan) { const maxHeight = getPlanLimits(plan).maxResolutionHeight; return RESOLUTIONS.filter((r) => r.height <= maxHeight); } export function isResolutionAllowedForPlan(resolution: string, plan: Plan): boolean { const res = getResolution(resolution); if (!res) return false; return res.height <= getPlanLimits(plan).maxResolutionHeight; } export function isAudioExtensionAllowed(filename: string, plan: Plan): boolean { const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase(); return getPlanLimits(plan).allowedAudioExtensions.includes(ext); } export function getNextMonthlyQuotaReset(from: Date = new Date()): Date { return new Date(from.getFullYear(), from.getMonth() + 1, 1, 0, 0, 0, 0); }