github.com/derat/nup@v0.0.0-20230418113745-15592ba7c620/cmd/nup/debug/command.go (about)

     1  // Copyright 2022 Daniel Erat.
     2  // All rights reserved.
     3  
     4  package debug
     5  
     6  import (
     7  	"context"
     8  	"flag"
     9  	"fmt"
    10  	"math"
    11  	"os"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/derat/nup/cmd/nup/client"
    16  	"github.com/google/subcommands"
    17  )
    18  
    19  type Command struct {
    20  	Cfg *client.Config
    21  
    22  	id3  bool // print all ID3v2 text frames
    23  	mpeg bool // read MPEG frames and print size/duration
    24  }
    25  
    26  func (*Command) Name() string     { return "debug" }
    27  func (*Command) Synopsis() string { return "print information about a song file" }
    28  func (*Command) Usage() string {
    29  	return `debug <flags> <path>...:
    30  	Print information about one or more song files.
    31  
    32  `
    33  }
    34  
    35  func (cmd *Command) SetFlags(f *flag.FlagSet) {
    36  	f.BoolVar(&cmd.id3, "id3", false, "Print all ID3v2 text frames")
    37  	f.BoolVar(&cmd.mpeg, "mpeg", false, "Read MPEG frames and print size/duration info")
    38  }
    39  
    40  func (cmd *Command) Execute(ctx context.Context, fs *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
    41  	if fs.NArg() < 1 {
    42  		fmt.Fprintln(os.Stderr, cmd.Usage())
    43  		return subcommands.ExitUsageError
    44  	}
    45  	if !cmd.id3 && !cmd.mpeg {
    46  		fmt.Fprintln(os.Stderr, "No action requested via flags")
    47  		return subcommands.ExitUsageError
    48  	}
    49  
    50  	var failed bool
    51  	for i, p := range fs.Args() {
    52  		if len(fs.Args()) > 1 {
    53  			if i > 0 {
    54  				fmt.Println()
    55  			}
    56  			fmt.Println(p)
    57  		}
    58  		if cmd.id3 {
    59  			if err := cmd.doID3(p); err != nil {
    60  				fmt.Fprintln(os.Stderr, "Failed reading ID3 tag:", err)
    61  				failed = true
    62  			}
    63  		}
    64  		if cmd.mpeg {
    65  			if err := cmd.doMPEG(p); err != nil {
    66  				fmt.Fprintln(os.Stderr, "Failed reading MPEG frames:", err)
    67  				failed = true
    68  			}
    69  		}
    70  	}
    71  	if failed {
    72  		return subcommands.ExitFailure
    73  	}
    74  	return subcommands.ExitSuccess
    75  }
    76  
    77  func (cmd *Command) doID3(p string) error {
    78  	frames, err := readID3Frames(p)
    79  	if err != nil {
    80  		return err
    81  	}
    82  	for _, frame := range frames {
    83  		var val string
    84  		if len(frame.fields) == 0 {
    85  			val = fmt.Sprintf("[%d bytes]", frame.size)
    86  		} else {
    87  			quoted := make([]string, len(frame.fields))
    88  			for i, s := range frame.fields {
    89  				quoted[i] = fmt.Sprintf("%q", s)
    90  			}
    91  			val = strings.Join(quoted, " ")
    92  		}
    93  		fmt.Println(frame.id + " " + val)
    94  	}
    95  	return nil
    96  }
    97  
    98  func (cmd *Command) doMPEG(p string) error {
    99  	info, err := getMPEGInfo(p)
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	fmt.Printf("%d bytes: %d header, %d data, %d footer (%v)\n",
   105  		info.size, info.header, info.size-info.header-info.footer, info.footer, info.sha1)
   106  	for _, s := range info.skipped {
   107  		fmt.Printf("  skipped %d-%d (%d) %v\n", s.offset, s.offset+s.size-1, s.size, s.err)
   108  	}
   109  
   110  	var actualBitrate string
   111  	if info.vbr {
   112  		actualBitrate = fmt.Sprintf("%0.1f kb/s VBR", info.avgKbitRate)
   113  	} else {
   114  		actualBitrate = fmt.Sprintf("%0.0f kb/s CBR", math.Round(info.avgKbitRate))
   115  	}
   116  
   117  	format := func(d time.Duration) string {
   118  		return fmt.Sprintf("%d:%06.3f", int(d.Minutes()), (d % time.Minute).Seconds())
   119  	}
   120  	var enc string
   121  	if info.xingEnc != "" {
   122  		enc = ", " + info.xingEnc
   123  	}
   124  	fmt.Printf("Xing:   %s (%d frames, %d data%s)\n",
   125  		format(info.xingDur), info.xingFrames, info.xingBytes, enc)
   126  	fmt.Printf("Actual: %s (%d frames, %d data, %s)\n",
   127  		format(info.actualDur), info.actualFrames, info.actualBytes, actualBitrate)
   128  	if info.emptyFrame >= 0 {
   129  		fmt.Printf("Audio:  %s (%d frames, then empty starting at offset %d)\n",
   130  			format(info.emptyTime), info.emptyFrame, info.emptyOffset)
   131  	}
   132  	return nil
   133  }