feat(render #36): real per-tier output height (360/540/720/1080/4K)
Build backend images / build content-svc (push) Failing after 50s
Build backend images / build file-svc (push) Failing after 57s
Build backend images / build gateway (push) Failing after 50s
Build backend images / build identity-svc (push) Failing after 58s
Build backend images / build notification-svc (push) Failing after 48s
Build backend images / build render-svc (push) Failing after 53s
Build backend images / build studio-svc (push) Failing after 1m2s
Build backend images / build content-svc (push) Failing after 50s
Build backend images / build file-svc (push) Failing after 57s
Build backend images / build gateway (push) Failing after 50s
Build backend images / build identity-svc (push) Failing after 58s
Build backend images / build notification-svc (push) Failing after 48s
Build backend images / build render-svc (push) Failing after 53s
Build backend images / build studio-svc (push) Failing after 1m2s
r_height was hardcoded 1080. render-svc now derives r_height from the resolution (ResolutionHeight) on job create; node-agent ffmpeg downscales to the tier height (scale=-2:H). Quality picker now actually changes output size. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -187,7 +187,25 @@ func ffmpegPath() string {
|
||||
|
||||
// transcodeToMP4 converts a lossless AE render (AVI/MOV) to a web-playable H.264
|
||||
// MP4 using ffmpeg. Returns the .mp4 path. Errors if ffmpeg is unavailable.
|
||||
func transcodeToMP4(ctx context.Context, src, requested string) (string, error) {
|
||||
// resolutionHeight maps a quality-tier label to its output height (mirrors render-svc).
|
||||
func resolutionHeight(resolution string) int {
|
||||
switch strings.ToLower(strings.TrimSpace(resolution)) {
|
||||
case "360p":
|
||||
return 360
|
||||
case "540p":
|
||||
return 540
|
||||
case "720p":
|
||||
return 720
|
||||
case "1080p", "fullhd":
|
||||
return 1080
|
||||
case "4k", "2160p":
|
||||
return 2160
|
||||
default:
|
||||
return 0 // unknown → no scaling
|
||||
}
|
||||
}
|
||||
|
||||
func transcodeToMP4(ctx context.Context, src, requested string, height int) (string, error) {
|
||||
ff := ffmpegPath()
|
||||
if ff == "" {
|
||||
return "", fmt.Errorf("ffmpeg not found (set FFMPEG_PATH or place ffmpeg.exe next to the agent)")
|
||||
@@ -196,10 +214,16 @@ func transcodeToMP4(ctx context.Context, src, requested string) (string, error)
|
||||
args := []string{
|
||||
"-y", "-i", src,
|
||||
"-c:v", "libx264", "-preset", "medium", "-crf", "20", "-pix_fmt", "yuv420p",
|
||||
}
|
||||
// Downscale (or up) to the selected quality tier. -2 keeps width even & aspect.
|
||||
if height > 0 {
|
||||
args = append(args, "-vf", fmt.Sprintf("scale=-2:%d", height))
|
||||
}
|
||||
args = append(args,
|
||||
"-c:a", "aac", "-b:a", "192k",
|
||||
"-movflags", "+faststart",
|
||||
dst,
|
||||
}
|
||||
)
|
||||
log.Printf("[ffmpeg] %s %v", ff, args)
|
||||
cmd := exec.CommandContext(ctx, ff, args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
@@ -281,7 +305,7 @@ func aeRender(ctx context.Context, aePath string, job *Job, outputPath string, o
|
||||
}
|
||||
// Transcode the lossless render → H.264 MP4 (much smaller, web-playable).
|
||||
_ = onProgress(ctx, 92, "Transcoding to MP4…")
|
||||
mp4, terr := transcodeToMP4(ctx, actual, outputPath)
|
||||
mp4, terr := transcodeToMP4(ctx, actual, outputPath, resolutionHeight(job.Resolution))
|
||||
if terr != nil {
|
||||
// ffmpeg missing/failed — fall back to the raw render so the job
|
||||
// still delivers a file (large, but valid).
|
||||
|
||||
@@ -3,6 +3,7 @@ package db
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/flatrender/render-svc/internal/models"
|
||||
@@ -410,6 +411,25 @@ func (s *Store) ListActiveJobs(ctx context.Context, userID uuid.UUID) ([]*models
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// ResolutionHeight maps a quality-tier label to its output height in pixels.
|
||||
// Used for r_height (stored) and the node's ffmpeg downscale.
|
||||
func ResolutionHeight(resolution string) int {
|
||||
switch strings.ToLower(strings.TrimSpace(resolution)) {
|
||||
case "360p":
|
||||
return 360
|
||||
case "540p":
|
||||
return 540
|
||||
case "720p":
|
||||
return 720
|
||||
case "1080p", "fullhd":
|
||||
return 1080
|
||||
case "4k", "2160p":
|
||||
return 2160
|
||||
default:
|
||||
return 1080
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) CreateJob(ctx context.Context, userID, tenantID uuid.UUID, req *models.RenderJobCreateRequest) (*models.RenderJob, error) {
|
||||
priceType := "Free"
|
||||
if req.PriceType != nil {
|
||||
@@ -436,12 +456,12 @@ func (s *Store) CreateJob(ctx context.Context, userID, tenantID uuid.UUID, req *
|
||||
-- to the saved-project id only if the lookup is somehow null.
|
||||
COALESCE((SELECT original_project_id FROM studio.saved_projects WHERE id = $3), $3),
|
||||
'paid'::render_priority_queue, 'Queued'::render_step, $4::price_kind,
|
||||
$5::render_quality, $6, 1080, $7, COALESCE($8, FALSE),
|
||||
$5::render_quality, $6, $11, $7, COALESCE($8, FALSE),
|
||||
0, 'FIX', $9, $10)
|
||||
RETURNING id`,
|
||||
tenantID, userID, req.SavedProjectID, priceType,
|
||||
req.Quality, req.Resolution, frameRate, req.Is60FPS,
|
||||
tellMe, req.PreferredRegion,
|
||||
tellMe, req.PreferredRegion, ResolutionHeight(req.Resolution),
|
||||
).Scan(&id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user