diff --git a/Info.go b/Info.go index 1f7adca..bc11407 100644 --- a/Info.go +++ b/Info.go @@ -100,6 +100,7 @@ type Fragment struct { XHeadSeqNum int Data *bytes.Buffer Slow bool + MimeType string } type seqChanInfo struct { @@ -905,6 +906,11 @@ func (di *DownloadInfo) downloadFragment(state *fragThreadState, dataChan chan<- headerSeqnum, _ = strconv.Atoi(headerSeqnumStr) } + mimeType := resp.Header.Get("Content-Type") + if !strings.HasSuffix(mimeType, "/mp4") && !strings.HasSuffix(mimeType, "/webm") { + LogTrace("%s: fragment %d has unknown MIME type '%s'", state.Name, state.SeqNum, mimeType) + } + if state.ToFile { err = os.WriteFile(fname, respData, 0644) if err != nil { @@ -936,6 +942,7 @@ func (di *DownloadInfo) downloadFragment(state *fragThreadState, dataChan chan<- FileName: fname, Data: data, Slow: isSlow, + MimeType: mimeType, } return @@ -1157,7 +1164,20 @@ func (di *DownloadInfo) DownloadStream(dataType, dataFile string, progressChan c buf := make([]byte, BufferSize) rc, _ := data.Data.Read(buf) - count, err := f.Write(RemoveSidx(buf[:rc])) + + writeBuf := buf + // ffmpeg doesn't like certain atoms in concatenated MP4 files, so we remove those here + // If MimeType is blank, assume MP4 + if strings.HasSuffix(data.MimeType, "/mp4") || data.MimeType == "" { + badAtoms := []string{"sidx"} + // ffmpeg 6.1 doesn't like multiple ftyp atoms, so only allow on the first fragment + if curFrag != startFrag { + badAtoms = append(badAtoms, "ftyp") + } + writeBuf = RemoveAtoms(buf[:rc], badAtoms...) + } + + count, err := f.Write(writeBuf) bytesWritten += count if err != nil { diff --git a/util.go b/util.go index da8953b..3c2d0e3 100644 --- a/util.go +++ b/util.go @@ -19,6 +19,7 @@ import ( "os/signal" "path/filepath" "regexp" + "sort" "strconv" "strings" "time" @@ -530,19 +531,31 @@ func GetAtoms(data []byte) map[string]Atom { return atoms } -func RemoveSidx(data []byte) []byte { +func RemoveAtoms(data []byte, atomList ...string) []byte { atoms := GetAtoms(data) - sidx, ok := atoms["sidx"] - if !ok { - return data + var atomsToRemove []Atom + for _, atomName := range atomList { + atom, ok := atoms[atomName] + if !ok { + continue + } + atomsToRemove = append(atomsToRemove, atom) } - ofs := sidx.Offset - rlen := sidx.Offset + sidx.Length - newData := append(data[:ofs], data[rlen:]...) + // Sort atoms by byte offset in descending order, + // this lets us remove them in order without affecting the next atom's offset + sort.Slice(atomsToRemove, func(i, j int) bool { + return atomsToRemove[i].Offset > atomsToRemove[j].Offset + }) - return newData + for _, atom := range atomsToRemove { + ofs := atom.Offset + rlen := atom.Offset + atom.Length + data = append(data[:ofs], data[rlen:]...) + } + + return data } func GetVideoIdFromWatchPage(data []byte) string {