package jobs import ( "context" "os/exec" "path/filepath" "time" "quyun/v2/app/jobs/args" "quyun/v2/database/models" "quyun/v2/pkg/consts" "quyun/v2/providers/storage" "github.com/riverqueue/river" log "github.com/sirupsen/logrus" ) // @provider(job) type MediaProcessWorker struct { river.WorkerDefaults[args.MediaProcessArgs] storage *storage.Storage } func (j *MediaProcessWorker) Work(ctx context.Context, job *river.Job[args.MediaProcessArgs]) error { arg := job.Args // 1. Fetch Asset asset, err := models.MediaAssetQuery.WithContext(ctx).Where(models.MediaAssetQuery.ID.Eq(arg.AssetID)).First() if err != nil { return err } // 2. Update status to processing _, err = models.MediaAssetQuery.WithContext(ctx). Where(models.MediaAssetQuery.ID.Eq(asset.ID)). UpdateSimple(models.MediaAssetQuery.Status.Value(consts.MediaAssetStatusProcessing)) if err != nil { return err } // 3. Process Video (FFmpeg) if asset.Type == consts.MediaAssetTypeVideo { if _, err := exec.LookPath("ffmpeg"); err == nil { localPath := j.storage.Config.LocalPath if localPath == "" { localPath = "./storage" } inputFile := filepath.Join(localPath, asset.ObjectKey) outputDir := filepath.Dir(inputFile) // Simple transcoding: convert to MP4 (mocking complex HLS for simplicity) // Or just extract cover coverKey := asset.ObjectKey + ".jpg" coverFile := filepath.Join(outputDir, filepath.Base(coverKey)) // Generate Cover cmd := exec.CommandContext( ctx, "ffmpeg", "-y", "-i", inputFile, "-ss", "00:00:01.000", "-vframes", "1", coverFile, ) if out, err := cmd.CombinedOutput(); err != nil { log.Errorf("ffmpeg failed: %s, output: %s", err, string(out)) // Don't fail the job, just skip cover } else { log.Infof("Generated cover: %s", coverFile) // TODO: Create MediaAsset for cover? Or update meta? } } else { log.Warn("ffmpeg not found, skipping real transcoding") } } // 4. Update status to ready _, err = models.MediaAssetQuery.WithContext(ctx). Where(models.MediaAssetQuery.ID.Eq(asset.ID)). Updates(&models.MediaAsset{ Status: consts.MediaAssetStatusReady, UpdatedAt: time.Now(), }) return err }