// // mp3chap // Copyright (c) 2017 joshua stein // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // 3. The name of the author may not be used to endorse or promote products // derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // package main import ( "fmt" id3 "github.com/jcs/id3-go" id3v2 "github.com/jcs/id3-go/v2" "log" "os" "strconv" ) type Chapter struct { element string startSecs uint32 endSecs uint32 title string } func usage() { fmt.Printf("%s: [ ...]\n", os.Args[0]) os.Exit(1) } func main() { if len(os.Args) < 4 { usage() } fn := os.Args[1] mp3, err := id3.Open(fn) if err != nil { log.Fatal("can't open %s: %s", fn, err) } chaps := make([]Chapter, 0) tocchaps := make([]string, 0) finalEnd := uint64(0) x := 2 for ; x < len(os.Args)-1; x += 2 { if x+1 > len(os.Args)-2+1 { usage() } startSecs, err := strconv.ParseUint(os.Args[x], 10, 32) if err != nil { log.Fatal("failed parsing seconds %#v: %v", os.Args[x], err) } element := fmt.Sprintf("chp%d", len(chaps)) tocchaps = append(tocchaps, element) chap := Chapter{element, uint32(startSecs * 1000), 0, os.Args[x+1]} chaps = append(chaps, chap) } // if there is a final odd arg, use it as the final chapter end if x < len(os.Args) { finalEnd, err = strconv.ParseUint(os.Args[x], 10, 32) if err != nil { log.Fatal("failed parsing final seconds %#v: %v", os.Args[x], err) } else { finalEnd = uint64(finalEnd * 1000) } } // each chapter ends where the next one starts for x := range chaps { if x < len(chaps)-1 { chaps[x].endSecs = chaps[x+1].startSecs } } if finalEnd == 0 { // and the last one ends when the file does tlenf := mp3.Frame("TLEN") if tlenf == nil { log.Fatal("can't find TLEN frame, don't know total duration") } tlenft, ok := tlenf.(*id3v2.TextFrame) if !ok { log.Fatal("can't convert TLEN to TextFrame") } tlen, err := strconv.ParseUint(tlenft.Text(), 10, 32) if err == nil { log.Fatal("can't parse TLEN value %#v\n", tlenft.Text()) } chaps[len(chaps)-1].endSecs = uint32(tlen) } else { chaps[len(chaps)-1].endSecs = uint32(finalEnd) } // ready to modify the file, clear out what's there mp3.DeleteFrames("CTOC") mp3.DeleteFrames("CHAP") // build a new TOC referencing each chapter ctocft := id3v2.V23FrameTypeMap["CTOC"] toc := id3v2.NewTOCFrame(ctocft, "toc", true, true, tocchaps) mp3.AddFrames(toc) // add each chapter chapft := id3v2.V23FrameTypeMap["CHAP"] for _, c := range chaps { ch := id3v2.NewChapterFrame(chapft, c.element, c.startSecs, c.endSecs, 0, 0, true, c.title, "", "") mp3.AddFrames(ch) } mp3.Close() }