//go:build llm_script && !scripts_pkg package main import ( "bytes" "encoding/binary" "encoding/json" "errors" "flag" "fmt" "image" "image/color" "image/draw" "image/gif" "image/png" "math" "os" "path/filepath" "sort" "strings" "time" ) type reportRow struct { Model string `json:"model"` Provider string `json:"provider"` Scene string `json:"scene"` Input string `json:"input,omitempty"` Output string `json:"output,omitempty"` Context string `json:"context,omitempty"` } type dailyReport struct { ReportDate string `json:"report_date"` Stats map[string]string `json:"stats"` International []reportRow `json:"international"` Domestic []reportRow `json:"domestic"` SourceReport string `json:"source_report"` GeneratedAt string `json:"generated_at,omitempty"` SelectedSection string `json:"selected_section,omitempty"` } type DigestCard struct { Slug string `json:"slug"` Title string `json:"title"` Headline string `json:"headline"` BulletLines []string `json:"bullet_lines"` Narration string `json:"narration"` FramePath string `json:"frame_path,omitempty"` ScriptPath string `json:"script_path,omitempty"` } type digestManifest struct { ReportDate string `json:"report_date"` SourceReport string `json:"source_report"` GeneratedAt string `json:"generated_at"` OutputDir string `json:"output_dir"` VideoPath string `json:"video_path"` AudioPath string `json:"audio_path"` Cards []DigestCard `json:"cards"` } var framePalette = color.Palette{ color.RGBA{12, 18, 28, 255}, color.RGBA{32, 48, 74, 255}, color.RGBA{91, 192, 190, 255}, color.RGBA{245, 247, 250, 255}, color.RGBA{255, 196, 61, 255}, color.RGBA{255, 107, 107, 255}, } var slideBackgrounds = []uint8{1, 2, 1, 4, 5} func main() { var reportPath string var reportDate string var outputDir string flag.StringVar(&reportPath, "report", "", "path to daily markdown report") flag.StringVar(&reportDate, "date", "", "report date in YYYY-MM-DD") flag.StringVar(&outputDir, "output-dir", "", "output directory for video digest artifacts") flag.Parse() resolvedReport, err := resolveReportPath(reportPath, reportDate) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } content, err := os.ReadFile(resolvedReport) if err != nil { fmt.Fprintf(os.Stderr, "read report failed: %v\n", err) os.Exit(1) } report, err := parseDailyReport(content) if err != nil { fmt.Fprintf(os.Stderr, "parse report failed: %v\n", err) os.Exit(1) } report.SourceReport = resolvedReport if reportDate == "" { reportDate = report.ReportDate } if reportDate == "" { reportDate = time.Now().Format("2006-01-02") } cards := buildDigestCards(report) if len(cards) == 0 { fmt.Fprintln(os.Stderr, "no digest cards generated") os.Exit(1) } if outputDir == "" { outputDir = filepath.Join(filepath.Dir(resolvedReport), "video", reportDate) } manifest, err := generateDigestArtifacts(report, cards, outputDir) if err != nil { fmt.Fprintf(os.Stderr, "generate digest artifacts failed: %v\n", err) os.Exit(1) } fmt.Printf("report=%s\n", manifest.SourceReport) fmt.Printf("output=%s\n", manifest.OutputDir) fmt.Printf("cards=%d\n", len(manifest.Cards)) fmt.Printf("video=%s\n", manifest.VideoPath) fmt.Printf("audio=%s\n", manifest.AudioPath) } func resolveReportPath(explicitPath string, reportDate string) (string, error) { if explicitPath != "" { return explicitPath, nil } root := "/home/long/project/llm-intelligence/reports/daily" if reportDate != "" { return filepath.Join(root, fmt.Sprintf("daily_report_%s.md", reportDate)), nil } matches, err := filepath.Glob(filepath.Join(root, "daily_report_*.md")) if err != nil { return "", err } if len(matches) == 0 { return "", errors.New("no daily report markdown files found") } sort.Strings(matches) return matches[len(matches)-1], nil } func parseDailyReport(content []byte) (dailyReport, error) { report := dailyReport{ Stats: make(map[string]string), } lines := strings.Split(string(content), "\n") section := "" for _, rawLine := range lines { line := strings.TrimSpace(rawLine) if strings.HasPrefix(line, "**报告日期**:") { report.ReportDate = strings.TrimSpace(strings.TrimPrefix(line, "**报告日期**:")) continue } switch line { case "## 📊 数据质量摘要": section = "stats" continue case "## 🌍 国际推荐模型 TOP 5": section = "international" continue case "## 🇨🇳 国内模型 TOP 10": section = "domestic" continue } if strings.HasPrefix(line, "## ") { section = "" continue } if !strings.HasPrefix(line, "|") || strings.Contains(line, "------") { continue } parts := splitMarkdownTableLine(line) switch section { case "stats": if len(parts) >= 2 && parts[0] != "指标" { report.Stats[parts[0]] = parts[1] } case "international": if row, ok := parseModelRow(parts); ok { report.International = append(report.International, row) } case "domestic": if row, ok := parseModelRow(parts); ok { report.Domestic = append(report.Domestic, row) } } } if report.ReportDate == "" { return report, errors.New("report date not found") } return report, nil } func splitMarkdownTableLine(line string) []string { trimmed := strings.Trim(line, "|") parts := strings.Split(trimmed, "|") out := make([]string, 0, len(parts)) for _, part := range parts { out = append(out, strings.TrimSpace(part)) } return out } func parseModelRow(parts []string) (reportRow, bool) { if len(parts) < 7 || parts[0] == "排名" { return reportRow{}, false } return reportRow{ Model: parts[1], Provider: parts[2], Scene: parts[3], Input: parts[4], Output: parts[5], Context: parts[6], }, true } func buildDigestCards(report dailyReport) []DigestCard { total := report.Stats["模型总数"] cny := report.Stats["CNY定价"] usd := report.Stats["USD定价"] codeRows := pickSceneRows(report, "代码") reasoningRows := pickSceneRows(report, "推理") visionRows := pickSceneRows(report, "视觉") cards := []DigestCard{ newDigestCard( "code", "Code Digest", fmt.Sprintf("%s total models tracked", total), codeRows, fmt.Sprintf("Code digest highlights %d candidate models. CNY priced entries %s.", len(codeRows), cny), ), newDigestCard( "reasoning", "Reasoning Digest", fmt.Sprintf("USD priced entries %s", usd), reasoningRows, fmt.Sprintf("Reasoning digest focuses on %d reasoning oriented models.", len(reasoningRows)), ), newDigestCard( "vision", "Vision Digest", fmt.Sprintf("CNY priced entries %s", cny), visionRows, fmt.Sprintf("Vision digest contains %d multimodal candidates from the latest report.", len(visionRows)), ), newDigestCard( "domestic", "Domestic Digest", fmt.Sprintf("Domestic pricing entries %s", cny), firstRows(report.Domestic, 3), fmt.Sprintf("Domestic digest summarizes top local platforms with %d highlighted entries.", min(3, len(report.Domestic))), ), newDigestCard( "global", "Global Digest", fmt.Sprintf("Global pricing entries %s", usd), firstRows(report.International, 3), fmt.Sprintf("Global digest summarizes top international recommendations with %d highlighted entries.", min(3, len(report.International))), ), } return cards } func newDigestCard(slug string, title string, headline string, rows []reportRow, narration string) DigestCard { bullets := make([]string, 0, len(rows)) for _, row := range rows { bullets = append(bullets, fmt.Sprintf("%s - %s - %s", row.Model, row.Provider, row.Scene)) } if len(bullets) == 0 { bullets = append(bullets, "No matching models in current report") } return DigestCard{ Slug: slug, Title: title, Headline: headline, BulletLines: bullets, Narration: narration, } } func pickSceneRows(report dailyReport, scene string) []reportRow { rows := make([]reportRow, 0, 3) for _, source := range [][]reportRow{report.Domestic, report.International} { for _, row := range source { if strings.Contains(row.Scene, scene) { rows = append(rows, row) } if len(rows) == 3 { return rows } } } return rows } func firstRows(rows []reportRow, n int) []reportRow { if len(rows) < n { n = len(rows) } out := make([]reportRow, 0, n) for i := 0; i < n; i++ { out = append(out, rows[i]) } return out } func generateDigestArtifacts(report dailyReport, cards []DigestCard, outputDir string) (digestManifest, error) { scriptDir := filepath.Join(outputDir, "scripts") frameDir := filepath.Join(outputDir, "frames") if err := os.MkdirAll(scriptDir, 0o755); err != nil { return digestManifest{}, err } if err := os.MkdirAll(frameDir, 0o755); err != nil { return digestManifest{}, err } frames := make([]*image.Paletted, 0, len(cards)) delays := make([]int, 0, len(cards)) for i, card := range cards { frame := renderCardFrame(card, i) framePath := filepath.Join(frameDir, fmt.Sprintf("%02d_%s.png", i+1, card.Slug)) if err := writePNG(framePath, frame); err != nil { return digestManifest{}, err } scriptPath := filepath.Join(scriptDir, fmt.Sprintf("%02d_%s.md", i+1, card.Slug)) if err := os.WriteFile(scriptPath, []byte(renderCardScript(card)), 0o644); err != nil { return digestManifest{}, err } card.FramePath = framePath card.ScriptPath = scriptPath cards[i] = card frames = append(frames, frame) delays = append(delays, 120) } videoPath := filepath.Join(outputDir, "video_digest.gif") if err := writeAnimatedGIF(videoPath, frames, delays); err != nil { return digestManifest{}, err } audioData, err := buildNarrationAudio(cards) if err != nil { return digestManifest{}, err } audioPath := filepath.Join(outputDir, "narration.wav") if err := os.WriteFile(audioPath, audioData, 0o644); err != nil { return digestManifest{}, err } manifest := digestManifest{ ReportDate: report.ReportDate, SourceReport: report.SourceReport, GeneratedAt: time.Now().Format(time.RFC3339), OutputDir: outputDir, VideoPath: videoPath, AudioPath: audioPath, Cards: cards, } manifestPath := filepath.Join(outputDir, "manifest.json") payload, err := json.MarshalIndent(manifest, "", " ") if err != nil { return digestManifest{}, err } if err := os.WriteFile(manifestPath, payload, 0o644); err != nil { return digestManifest{}, err } return manifest, nil } func renderCardScript(card DigestCard) string { var b strings.Builder b.WriteString("# " + card.Title + "\n\n") b.WriteString("## Headline\n") b.WriteString("- " + card.Headline + "\n\n") b.WriteString("## Narration\n") b.WriteString("- " + card.Narration + "\n\n") b.WriteString("## Bullet Lines\n") for _, line := range card.BulletLines { b.WriteString("- " + line + "\n") } return b.String() } func writePNG(path string, img image.Image) error { f, err := os.Create(path) if err != nil { return err } defer f.Close() return png.Encode(f, img) } func writeAnimatedGIF(path string, frames []*image.Paletted, delays []int) error { f, err := os.Create(path) if err != nil { return err } defer f.Close() return gif.EncodeAll(f, &gif.GIF{Image: frames, Delay: delays, LoopCount: 0}) } func renderCardFrame(card DigestCard, index int) *image.Paletted { rect := image.Rect(0, 0, 640, 360) img := image.NewPaletted(rect, framePalette) bg := slideBackgrounds[index%len(slideBackgrounds)] draw.Draw(img, rect, &image.Uniform{framePalette[bg]}, image.Point{}, draw.Src) fillRect(img, 18, 18, 622, 342, 0) fillRect(img, 28, 28, 612, 88, bg) fillRect(img, 28, 104, 612, 332, 1) drawRasterText(img, 40, 42, 3, sanitizeFrameText(card.Title), 3) drawRasterText(img, 40, 116, 2, sanitizeFrameText(card.Headline), 4) for i, line := range firstStrings(card.BulletLines, 3) { drawRasterText(img, 40, 160+i*42, 2, sanitizeFrameText(line), 3) } drawRasterText(img, 40, 302, 1, sanitizeFrameText("LLM INTELLIGENCE VIDEO DIGEST"), 4) return img } func firstStrings(lines []string, n int) []string { if len(lines) < n { n = len(lines) } out := make([]string, 0, n) for i := 0; i < n; i++ { out = append(out, lines[i]) } return out } func sanitizeFrameText(input string) string { upper := strings.ToUpper(input) var b strings.Builder for _, r := range upper { if _, ok := glyphs[r]; ok { b.WriteRune(r) continue } switch { case r >= 'A' && r <= 'Z': b.WriteRune(r) case r >= '0' && r <= '9': b.WriteRune(r) default: b.WriteRune(' ') } } return strings.Join(strings.Fields(b.String()), " ") } func fillRect(img *image.Paletted, x1 int, y1 int, x2 int, y2 int, idx uint8) { for y := y1; y < y2; y++ { for x := x1; x < x2; x++ { img.SetColorIndex(x, y, idx) } } } func drawRasterText(img *image.Paletted, x int, y int, scale int, text string, idx uint8) { cursor := x for _, r := range text { pattern, ok := glyphs[r] if !ok { pattern = glyphs[' '] } for row, bits := range pattern { for col := 0; col < 5; col++ { if bits&(1<<(4-col)) == 0 { continue } fillRect(img, cursor+col*scale, y+row*scale, cursor+(col+1)*scale, y+(row+1)*scale, idx) } } cursor += 6 * scale } } func buildNarrationAudio(cards []DigestCard) ([]byte, error) { const sampleRate = 16000 var pcm []int16 for i, card := range cards { freq := 330.0 + float64(i)*55.0 duration := 0.9 + float64(len(card.BulletLines))*0.18 pcm = append(pcm, synthTone(freq, duration, sampleRate)...) pcm = append(pcm, make([]int16, sampleRate/5)...) } return encodeWAV(pcm, sampleRate), nil } func synthTone(freq float64, duration float64, sampleRate int) []int16 { samples := int(duration * float64(sampleRate)) out := make([]int16, 0, samples) for i := 0; i < samples; i++ { t := float64(i) / float64(sampleRate) envelope := 1.0 if i < sampleRate/50 { envelope = float64(i) / float64(sampleRate/50) } if i > samples-sampleRate/25 { remaining := samples - i if remaining > 0 { envelope = minFloat(envelope, float64(remaining)/float64(sampleRate/25)) } } value := math.Sin(2*math.Pi*freq*t) + 0.35*math.Sin(2*math.Pi*(freq/2)*t) out = append(out, int16(value*envelope*12000)) } return out } func encodeWAV(samples []int16, sampleRate int) []byte { const channels = 1 const bitsPerSample = 16 dataSize := len(samples) * 2 byteRate := sampleRate * channels * bitsPerSample / 8 blockAlign := channels * bitsPerSample / 8 var buf bytes.Buffer buf.WriteString("RIFF") _ = binary.Write(&buf, binary.LittleEndian, uint32(36+dataSize)) buf.WriteString("WAVE") buf.WriteString("fmt ") _ = binary.Write(&buf, binary.LittleEndian, uint32(16)) _ = binary.Write(&buf, binary.LittleEndian, uint16(1)) _ = binary.Write(&buf, binary.LittleEndian, uint16(channels)) _ = binary.Write(&buf, binary.LittleEndian, uint32(sampleRate)) _ = binary.Write(&buf, binary.LittleEndian, uint32(byteRate)) _ = binary.Write(&buf, binary.LittleEndian, uint16(blockAlign)) _ = binary.Write(&buf, binary.LittleEndian, uint16(bitsPerSample)) buf.WriteString("data") _ = binary.Write(&buf, binary.LittleEndian, uint32(dataSize)) for _, sample := range samples { _ = binary.Write(&buf, binary.LittleEndian, sample) } return buf.Bytes() } func min(a int, b int) int { if a < b { return a } return b } func minFloat(a float64, b float64) float64 { if a < b { return a } return b } var glyphs = map[rune][7]uint8{ ' ': {0, 0, 0, 0, 0, 0, 0}, '-': {0, 0, 0, 31, 0, 0, 0}, '.': {0, 0, 0, 0, 0, 12, 12}, ':': {0, 12, 12, 0, 12, 12, 0}, '/': {1, 2, 4, 8, 16, 0, 0}, '+': {0, 4, 4, 31, 4, 4, 0}, '(': {2, 4, 8, 8, 8, 4, 2}, ')': {8, 4, 2, 2, 2, 4, 8}, '0': {14, 17, 19, 21, 25, 17, 14}, '1': {4, 12, 4, 4, 4, 4, 14}, '2': {14, 17, 1, 2, 4, 8, 31}, '3': {30, 1, 1, 14, 1, 1, 30}, '4': {2, 6, 10, 18, 31, 2, 2}, '5': {31, 16, 16, 30, 1, 1, 30}, '6': {14, 16, 16, 30, 17, 17, 14}, '7': {31, 1, 2, 4, 8, 8, 8}, '8': {14, 17, 17, 14, 17, 17, 14}, '9': {14, 17, 17, 15, 1, 1, 14}, 'A': {14, 17, 17, 31, 17, 17, 17}, 'B': {30, 17, 17, 30, 17, 17, 30}, 'C': {14, 17, 16, 16, 16, 17, 14}, 'D': {28, 18, 17, 17, 17, 18, 28}, 'E': {31, 16, 16, 30, 16, 16, 31}, 'F': {31, 16, 16, 30, 16, 16, 16}, 'G': {14, 17, 16, 16, 19, 17, 15}, 'H': {17, 17, 17, 31, 17, 17, 17}, 'I': {14, 4, 4, 4, 4, 4, 14}, 'J': {7, 2, 2, 2, 18, 18, 12}, 'K': {17, 18, 20, 24, 20, 18, 17}, 'L': {16, 16, 16, 16, 16, 16, 31}, 'M': {17, 27, 21, 17, 17, 17, 17}, 'N': {17, 25, 21, 19, 17, 17, 17}, 'O': {14, 17, 17, 17, 17, 17, 14}, 'P': {30, 17, 17, 30, 16, 16, 16}, 'Q': {14, 17, 17, 17, 21, 18, 13}, 'R': {30, 17, 17, 30, 20, 18, 17}, 'S': {15, 16, 16, 14, 1, 1, 30}, 'T': {31, 4, 4, 4, 4, 4, 4}, 'U': {17, 17, 17, 17, 17, 17, 14}, 'V': {17, 17, 17, 17, 17, 10, 4}, 'W': {17, 17, 17, 17, 21, 27, 17}, 'X': {17, 17, 10, 4, 10, 17, 17}, 'Y': {17, 17, 10, 4, 4, 4, 4}, 'Z': {31, 1, 2, 4, 8, 16, 31}, }