| // Copyright 2022 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // ffmpeg_md5sum wraps ffmpeg to reoutput MD5 sums from decoding, one per line. |
| |
| package main |
| |
| import ( |
| "bytes" |
| "context" |
| "errors" |
| "flag" |
| "fmt" |
| "os" |
| "os/exec" |
| "strings" |
| ) |
| |
| const ffmpegPath = "/usr/local/bin/ffmpeg" |
| |
| // multiFlags allow an alternative to passing space-separated flags, so that |
| // --f v1 v2 can instead be passed as --f v1 --f v2. |
| // The Golang flag package cannot parse space-sparated flags without enclosing |
| // the values in quotation marks. However, the platform decoding tests nest |
| // binary calls and flags, complicating quotations. |
| // This alternative provides a way to unnest/unfold the flags. |
| type multiFlags []string |
| |
| // String implements the flag interface. |
| func (m *multiFlags) String() string { |
| return strings.Join(*m, " ") |
| } |
| |
| // Set implements the flag interface. |
| func (m *multiFlags) Set(value string) error { |
| *m = append(*m, value) |
| return nil |
| } |
| |
| // parseHashes parses hashes from ffmpeg output. |
| func parseHashes(stdout string) ([]string, error) { |
| lines := strings.Split(stdout, "\n") |
| hashes := make([]string, 0, len(lines)) |
| |
| for _, l := range lines { |
| l = strings.TrimSpace(l) |
| if len(l) == 0 || strings.HasPrefix(l, "#") { |
| continue |
| } |
| |
| tok := strings.Split(l, ",") |
| hash := strings.TrimSpace(tok[len(tok)-1]) |
| if len(hash) != 32 { |
| return nil, errors.New(fmt.Sprintf("expected MD5 sum, got %v", hash)) |
| } |
| hashes = append(hashes, tok[len(tok)-1]) |
| } |
| return hashes, nil |
| } |
| |
| // exitWithError dumps all output to the appropriate streams and exits with errcode 1. |
| func exitWithError(stdout, stderr string, err error) { |
| fmt.Fprintf(os.Stdout, stdout) |
| fmt.Fprintf(os.Stderr, stderr) |
| fmt.Fprintf(os.Stderr, err.Error()) |
| os.Exit(1) |
| } |
| |
| func main() { |
| flag.Usage = func() { |
| fmt.Fprintf(os.Stderr, |
| "ffmpeg_md5sum is a wrapper around ffmpeg that generates MD5 sums from "+ |
| "decoding, and processes that (annotated) output to output instead one "+ |
| "hash per line.\n\n"+ |
| "Flags:\n"+ |
| "\tvideo: Required. Path to video to decode.\n"+ |
| "\tflags: Optional. Additional flags to ffmpeg. Pass space-separated\n"+ |
| "\t flags individually, i.e. `--flags -hwaccel --flags vaapi`\n"+ |
| "\t to pass `-hwaccel vaapi` to ffmpeg.\n") |
| } |
| |
| var flags multiFlags |
| var video string |
| flag.Var(&flags, "flags", "additional flags to ffmpeg: for space-separated flags, pass each individually with --flags") |
| flag.StringVar(&video, "video", "", "path to video to decode") |
| flag.Parse() |
| |
| args := append(flags, []string{ |
| "-hide_banner", |
| "-loglevel", "verbose", |
| "-i", video, |
| "-vf", "format=pix_fmts=yuv420p", |
| "-f", "framemd5", "-", |
| }...) |
| ctx := context.Background() |
| cmd := exec.CommandContext(ctx, ffmpegPath, args...) |
| |
| fmt.Fprintf(os.Stderr, "Running `%s %s`\n", ffmpegPath, strings.Join(args, " ")) |
| var outbuf, errbuf bytes.Buffer |
| cmd.Stdout, cmd.Stderr = &outbuf, &errbuf |
| err := cmd.Run() |
| stdout, stderr := outbuf.String(), errbuf.String() |
| if err != nil { |
| exitWithError(stdout, stderr, err) |
| } |
| |
| var hashes []string |
| if hashes, err = parseHashes(stdout); err != nil { |
| exitWithError(stdout, "", err) |
| } |
| fmt.Println(strings.Join(hashes, "\n")) |
| } |