func main() { // Parse flags flag.Parse() // Move all logging output to stderr, as output image will occupy // the stdout stream log.SetOutput(os.Stderr) log.SetPrefix(app + ": ") // Create image background color from input hex color string, or default // to black if invalid colorR, colorG, colorB := hexToRGB(*strBGColor) bgColor := color.RGBA{colorR, colorG, colorB, 255} // Create image foreground color from input hex color string, or default // to black if invalid colorR, colorG, colorB = hexToRGB(*strFGColor) fgColor := color.RGBA{colorR, colorG, colorB, 255} // Create image alternate color from input hex color string, or default // to foreground color if empty altColor := fgColor if *strAltColor != "" { colorR, colorG, colorB = hexToRGB(*strAltColor) altColor = color.RGBA{colorR, colorG, colorB, 255} } // Set of available functions fnSet := map[string]waveform.ColorFunc{ fnChecker: waveform.CheckerColor(fgColor, altColor, 10), fnFuzz: waveform.FuzzColor(fgColor, altColor), fnGradient: waveform.GradientColor(fgColor, altColor), fnSolid: waveform.SolidColor(fgColor), fnStripe: waveform.StripeColor(fgColor, altColor), } // Validate user-selected function colorFn, ok := fnSet[*strFn] if !ok { log.Fatalf("unknown function: %q %s", *strFn, fnOptions) } // Generate a waveform image from stdin, using values passed from // flags as options img, err := waveform.Generate(os.Stdin, waveform.BGColorFunction(waveform.SolidColor(bgColor)), waveform.FGColorFunction(colorFn), waveform.Resolution(*resolution), waveform.Scale(*scaleX, *scaleY), waveform.ScaleClipping(), waveform.Sharpness(*sharpness), ) if err != nil { // Set of known errors knownErr := map[error]struct{}{ waveform.ErrFormat: struct{}{}, waveform.ErrInvalidData: struct{}{}, waveform.ErrUnexpectedEOS: struct{}{}, } // On known error, fatal log if _, ok := knownErr[err]; ok { log.Fatal(err) } // Unknown errors, panic panic(err) } // Encode results as PNG to stdout if err := png.Encode(os.Stdout, img); err != nil { panic(err) } }
// GetWaveform generates and returns a waveform image from wavepipe. On success, this API will // return a binary stream. On failure, it will return a JSON error. func GetWaveform(w http.ResponseWriter, r *http.Request) { // Retrieve render ren := context.Get(r, CtxRender).(*render.Render) // Check API version if version, ok := mux.Vars(r)["version"]; ok { // Check if this API call is supported in the advertised version if !apiVersionSet.Has(version) { ren.JSON(w, 400, errRes(400, "unsupported API version: "+version)) return } } // Check for an ID parameter pID, ok := mux.Vars(r)["id"] if !ok { ren.JSON(w, 400, errRes(400, "no integer song ID provided")) return } // Verify valid integer ID id, err := strconv.Atoi(pID) if err != nil { ren.JSON(w, 400, errRes(400, "invalid integer song ID")) return } // Check for a pre-existing set of waveform values var values []float64 if tmpValues, ok := waveformLRU.Get(id); ok { // Use existing values values = tmpValues.([]float64) } else { // No existing waveform // Attempt to load the song with matching ID song := &data.Song{ID: id} if err := song.Load(); err != nil { // Check for invalid ID if err == sql.ErrNoRows { ren.JSON(w, 404, errRes(404, "song ID not found")) return } // All other errors log.Println(err) ren.JSON(w, 500, serverErr) return } // Open song's backing stream stream, err := song.Stream() if err != nil { log.Println(err) ren.JSON(w, 500, serverErr) return } // Generate a waveform object wave, err := waveform.New(stream, waveform.Resolution(4)) if err != nil { log.Println(err) ren.JSON(w, 500, serverErr) return } // Compute waveform values from input stream tmpValues, err := wave.Compute() if err != nil { // If unknown format, return JSON error if err == waveform.ErrFormat { ren.JSON(w, 501, errRes(501, "unsupported audio format")) return } log.Println(err) ren.JSON(w, 500, serverErr) return } // Cache values, and use for generating current and future images waveformLRU.Add(id, tmpValues) values = tmpValues } // Check for optional color parameters var cR, cG, cB uint8 // Background color var bgColor color.Color = color.White if bgColorStr := r.URL.Query().Get("bg"); bgColorStr != "" { // Convert %23 to # bgColorStr, err := url.QueryUnescape(bgColorStr) if err == nil { cR, cG, cB = hexToRGB(bgColorStr) bgColor = color.RGBA{cR, cG, cB, 255} } } // Foreground color var fgColor color.Color = color.Black if fgColorStr := r.URL.Query().Get("fg"); fgColorStr != "" { // Convert %23 to # fgColorStr, err := url.QueryUnescape(fgColorStr) if err == nil { cR, cG, cB = hexToRGB(fgColorStr) fgColor = color.RGBA{cR, cG, cB, 255} } } // Alternate color; follow foreground color by default var altColor color.Color = fgColor if altColorStr := r.URL.Query().Get("alt"); altColorStr != "" { // Convert %23 to # altColorStr, err := url.QueryUnescape(altColorStr) if err == nil { cR, cG, cB = hexToRGB(altColorStr) altColor = color.RGBA{cR, cG, cB, 255} } } // If requested, resize the image to the specified width var sizeX, sizeY int if strSize := r.URL.Query().Get("size"); strSize != "" { // Check for dimensions in two integers if _, err := fmt.Sscanf(strSize, "%dx%d", &sizeX, &sizeY); err != nil { ren.JSON(w, 400, errRes(400, "invalid x-separated integer pair for size")) return } } // Generate waveform object with sane defaults and user settings wave, err := waveform.New(nil, waveform.BGColorFunction(waveform.SolidColor(bgColor)), waveform.FGColorFunction(waveform.StripeColor(fgColor, altColor)), waveform.Scale(5, 4), waveform.Sharpness(1), waveform.ScaleClipping(), ) if err != nil { log.Println(err) ren.JSON(w, 500, serverErr) return } // Generate waveform image from computed values, with specified options img := wave.Draw(values) // If a resize option was set, perform it now if sizeX > 0 { // Perform image resize img = resize.Resize(uint(sizeX), uint(sizeY), img, resize.NearestNeighbor) } // Encode as PNG to HTTP writer if err := png.Encode(w, img); err != nil { log.Println(err) } }