mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Switch from mediainfo to ffprobe
This commit is contained in:
parent
32ccc54310
commit
c11e1cc8f0
@ -32,7 +32,6 @@ in
|
||||
go
|
||||
wgo
|
||||
mediainfo
|
||||
libmediainfo
|
||||
ffmpeg-full
|
||||
postgresql_15
|
||||
pgformatter
|
||||
|
@ -20,7 +20,7 @@ ENV SSL_CERT_DIR=/etc/ssl/certs
|
||||
RUN update-ca-certificates
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
ffmpeg libavformat-dev libavutil-dev libswscale-dev libmediainfo-dev \
|
||||
ffmpeg libavformat-dev libavutil-dev libswscale-dev \
|
||||
&& apt-get clean autoclean -y \
|
||||
&& apt-get autoremove -y
|
||||
WORKDIR /app
|
||||
@ -42,7 +42,7 @@ RUN sed -i -e's/ main/ main contrib non-free/g' /etc/apt/sources.list.d/debian.s
|
||||
RUN set -x && apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
# runtime dependencies
|
||||
ffmpeg libmediainfo-dev \
|
||||
ffmpeg \
|
||||
# hwaccel dependencies
|
||||
vainfo mesa-va-drivers \
|
||||
# intel hwaccel dependencies, not available everywhere
|
||||
|
@ -31,9 +31,9 @@ RUN sed -i -e's/ main/ main contrib non-free/g' /etc/apt/sources.list.d/debian.s
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
# runtime dependencies
|
||||
ffmpeg libavformat-dev \
|
||||
ffmpeg \
|
||||
# build dependencies
|
||||
libavutil-dev libswscale-dev libmediainfo-dev \
|
||||
libavformat-dev libavutil-dev libswscale-dev \
|
||||
# hwaccel dependencies
|
||||
vainfo mesa-va-drivers \
|
||||
# intel hwaccel dependencies, not available everywhere
|
||||
|
@ -12,13 +12,12 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/zoriya/go-mediainfo v0.0.0-20240113011752-07018f07efae
|
||||
gitlab.com/opennota/screengen v1.0.2
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/image v0.10.0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
gopkg.in/vansante/go-ffprobe.v2 v2.2.0
|
||||
)
|
||||
|
||||
require golang.org/x/image v0.10.0 // indirect
|
||||
|
@ -22,8 +22,6 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zoriya/go-mediainfo v0.0.0-20240113011752-07018f07efae h1:Xmtcb/GrG1jcLxC3cDFwdAM1oV7fLNBcN3h3YL+gN6g=
|
||||
github.com/zoriya/go-mediainfo v0.0.0-20240113011752-07018f07efae/go.mod h1:jzun1oQGoJSh65g1XKaolTmjd6HW/34WHH7VMdJdbvM=
|
||||
gitlab.com/opennota/screengen v1.0.2 h1:GxYTJdAPEzmg5v5CV4dgn45JVW+EcXXAvCxhE7w6UDw=
|
||||
gitlab.com/opennota/screengen v1.0.2/go.mod h1:4kED4yriw2zslwYmXFCa5qCvEKwleBA7l5OE+d94NTU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@ -71,5 +69,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/vansante/go-ffprobe.v2 v2.2.0 h1:iuOqTsbfYuqIz4tAU9NWh22CmBGxlGHdgj4iqP+NUmY=
|
||||
gopkg.in/vansante/go-ffprobe.v2 v2.2.0/go.mod h1:qF0AlAjk7Nqzqf3y333Ly+KxN3cKF2JqA3JT5ZheUGE=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -1,12 +1,11 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/zoriya/go-mediainfo"
|
||||
"gopkg.in/vansante/go-ffprobe.v2"
|
||||
)
|
||||
|
||||
// convert mediainfo to RFC 6381, waiting for either of those tickets to be resolved:
|
||||
@ -15,19 +14,13 @@ import (
|
||||
// https://trac.ffmpeg.org/ticket/6617
|
||||
//
|
||||
// this code is addapted from https://github.com/jellyfin/jellyfin/blob/master/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
|
||||
func GetMimeCodec(mi *mediainfo.File, kind mediainfo.StreamKind, i int) *string {
|
||||
codec := cmp.Or(
|
||||
mi.Parameter(kind, i, "InternetMediaType"),
|
||||
mi.Parameter(kind, i, "Format"),
|
||||
)
|
||||
|
||||
switch codec {
|
||||
case "video/H264", "AVC":
|
||||
// and https://git.ffmpeg.org/gitweb/ffmpeg.git/blob/HEAD%3a/libavformat/hlsenc.c#l344
|
||||
func GetMimeCodec(stream *ffprobe.Stream) *string {
|
||||
switch stream.CodecName {
|
||||
case "h264":
|
||||
ret := "avc1"
|
||||
info := strings.Split(strings.ToLower(mi.Parameter(kind, i, "Format_Profile")), "@")
|
||||
|
||||
format := info[0]
|
||||
switch format {
|
||||
switch strings.ToLower(stream.Profile) {
|
||||
case "high":
|
||||
ret += ".6400"
|
||||
case "main":
|
||||
@ -39,37 +32,30 @@ func GetMimeCodec(mi *mediainfo.File, kind mediainfo.StreamKind, i int) *string
|
||||
ret += ".4240"
|
||||
}
|
||||
|
||||
// level format is l3.1 for level 31
|
||||
level := ParseFloat(info[1][1:])
|
||||
ret += fmt.Sprintf("%02x", int(level*10))
|
||||
ret += fmt.Sprintf("%02x", stream.Level)
|
||||
return &ret
|
||||
|
||||
case "video/H265", "HEVC":
|
||||
case "h265":
|
||||
// The h265 syntax is a bit of a mystery at the time this comment was written.
|
||||
// This is what I've found through various sources:
|
||||
// FORMAT: [codecTag].[profile].[constraint?].L[level * 30].[UNKNOWN]
|
||||
ret := "hvc1"
|
||||
info := strings.Split(strings.ToLower(mi.Parameter(kind, i, "Format_Profile")), "@")
|
||||
|
||||
profile := info[0]
|
||||
if profile == "main 10" {
|
||||
if stream.Profile == "main 10" {
|
||||
ret += ".2.4"
|
||||
} else {
|
||||
ret += ".1.4"
|
||||
}
|
||||
|
||||
level := ParseFloat(info[1][:1])
|
||||
ret += fmt.Sprintf(".L%02X.BO", int(level*30))
|
||||
ret += fmt.Sprintf(".L%02X.BO", stream.Level)
|
||||
return &ret
|
||||
|
||||
case "AV1":
|
||||
case "av1":
|
||||
// https://aomedia.org/av1/specification/annex-a/
|
||||
// FORMAT: [codecTag].[profile].[level][tier].[bitDepth]
|
||||
ret := "av01"
|
||||
info := strings.Split(strings.ToLower(mi.Parameter(kind, i, "Format_Profile")), "@")
|
||||
|
||||
profile := info[0]
|
||||
switch profile {
|
||||
switch strings.ToLower(stream.Profile) {
|
||||
case "main":
|
||||
ret += ".0"
|
||||
case "high":
|
||||
@ -77,30 +63,24 @@ func GetMimeCodec(mi *mediainfo.File, kind mediainfo.StreamKind, i int) *string
|
||||
case "professional":
|
||||
ret += ".2"
|
||||
default:
|
||||
// Default to Main
|
||||
ret += ".0"
|
||||
}
|
||||
|
||||
// level is not defined in mediainfo. using a default
|
||||
// Default to the maximum defined level 6.3
|
||||
level := 19
|
||||
|
||||
bitdepth := ParseUint(mi.Parameter(kind, i, "BitDepth"))
|
||||
// not sure about this field, we want pixel bit depth
|
||||
bitdepth := ParseUint(stream.BitsPerRawSample)
|
||||
if bitdepth != 8 && bitdepth != 10 && bitdepth != 12 {
|
||||
// Default to 8 bits
|
||||
bitdepth = 8
|
||||
}
|
||||
|
||||
tierflag := 'M'
|
||||
ret += fmt.Sprintf(".%02X%c.%02d", level, tierflag, bitdepth)
|
||||
ret += fmt.Sprintf(".%02X%c.%02d", stream.Level, tierflag, bitdepth)
|
||||
|
||||
return &ret
|
||||
|
||||
case "AAC":
|
||||
case "aac":
|
||||
ret := "mp4a"
|
||||
|
||||
profile := strings.ToLower(mi.Parameter(kind, i, "Format_AdditionalFeatures"))
|
||||
switch profile {
|
||||
switch strings.ToLower(stream.Profile) {
|
||||
case "he":
|
||||
ret += ".40.5"
|
||||
case "lc":
|
||||
@ -111,24 +91,24 @@ func GetMimeCodec(mi *mediainfo.File, kind mediainfo.StreamKind, i int) *string
|
||||
|
||||
return &ret
|
||||
|
||||
case "audio/opus", "Opus":
|
||||
case "opus":
|
||||
ret := "Opus"
|
||||
return &ret
|
||||
|
||||
case "AC-3":
|
||||
case "ac-3":
|
||||
ret := "mp4a.a5"
|
||||
return &ret
|
||||
|
||||
case "audio/eac3", "E-AC-3":
|
||||
case "eac3", "E-AC-3":
|
||||
ret := "mp4a.a6"
|
||||
return &ret
|
||||
|
||||
case "audio/x-flac", "FLAC":
|
||||
case "x-flac", "FLAC":
|
||||
ret := "fLaC"
|
||||
return &ret
|
||||
|
||||
default:
|
||||
log.Printf("No known mime format for: %s", codec)
|
||||
log.Printf("No known mime format for: %s", stream.CodecName)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@ -13,9 +13,9 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
"time"
|
||||
|
||||
"github.com/zoriya/go-mediainfo"
|
||||
"gopkg.in/vansante/go-ffprobe.v2"
|
||||
)
|
||||
|
||||
type MediaInfo struct {
|
||||
@ -136,15 +136,6 @@ func ParseUint64(str string) uint64 {
|
||||
return i
|
||||
}
|
||||
|
||||
func ParseTime(str string) float32 {
|
||||
x := strings.Split(str, ":")
|
||||
hours, minutes, sms := ParseFloat(x[0]), ParseFloat(x[1]), x[2]
|
||||
y := strings.Split(sms, ".")
|
||||
seconds, ms := ParseFloat(y[0]), ParseFloat(y[1])
|
||||
|
||||
return (hours*60.+minutes)*60. + seconds + ms/1000.
|
||||
}
|
||||
|
||||
func Map[T, U any](ts []T, f func(T, int) U) []U {
|
||||
us := make([]U, len(ts))
|
||||
for i := range ts {
|
||||
@ -153,6 +144,25 @@ func Map[T, U any](ts []T, f func(T, int) U) []U {
|
||||
return us
|
||||
}
|
||||
|
||||
func MapStream[T any](streams []*ffprobe.Stream, kind ffprobe.StreamType, mapper func(*ffprobe.Stream, uint32) T) []T {
|
||||
count := 0
|
||||
for _, stream := range streams {
|
||||
if stream.CodecType == string(kind) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
ret := make([]T, count)
|
||||
|
||||
i := uint32(0)
|
||||
for _, stream := range streams {
|
||||
if stream.CodecType == string(kind) {
|
||||
ret[i] = mapper(stream, i)
|
||||
i++
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func OrNull(str string) *string {
|
||||
if str == "" {
|
||||
return nil
|
||||
@ -181,11 +191,11 @@ func GetInfo(path string, sha string) (*MediaInfo, error) {
|
||||
mi.ready.Add(1)
|
||||
go func() {
|
||||
save_path := fmt.Sprintf("%s/%s/info.json", Settings.Metadata, sha)
|
||||
if err := getSavedInfo(save_path, mi.info); err == nil {
|
||||
// if err := getSavedInfo(save_path, mi.info); err == nil {
|
||||
log.Printf("Using mediainfo cache on filesystem for %s", path)
|
||||
mi.ready.Done()
|
||||
return
|
||||
}
|
||||
// mi.ready.Done()
|
||||
// return
|
||||
// }
|
||||
|
||||
var val *MediaInfo
|
||||
val, err = getInfo(path)
|
||||
@ -229,65 +239,45 @@ func saveInfo[T any](save_path string, mi *T) error {
|
||||
func getInfo(path string) (*MediaInfo, error) {
|
||||
defer printExecTime("mediainfo for %s", path)()
|
||||
|
||||
mi, err := mediainfo.Open(path)
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancelFn()
|
||||
|
||||
mi, err := ffprobe.ProbeURL(ctx, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer mi.Close()
|
||||
|
||||
chapters_begin := ParseUint(mi.Parameter(mediainfo.StreamMenu, 0, "Chapters_Pos_Begin"))
|
||||
chapters_end := ParseUint(mi.Parameter(mediainfo.StreamMenu, 0, "Chapters_Pos_End"))
|
||||
|
||||
attachments := strings.Split(mi.Parameter(mediainfo.StreamGeneral, 0, "Attachments"), " / ")
|
||||
if len(attachments) == 1 && attachments[0] == "" {
|
||||
attachments = make([]string, 0)
|
||||
}
|
||||
|
||||
// fmt.Printf("%s", mi.Option("info_parameters", ""))
|
||||
|
||||
// duration in seconds
|
||||
duration := ParseFloat(mi.Parameter(mediainfo.StreamGeneral, 0, "Duration")) / 1000
|
||||
ret := MediaInfo{
|
||||
Path: path,
|
||||
// Remove leading .
|
||||
Extension: filepath.Ext(path)[1:],
|
||||
Size: ParseUint64(mi.Parameter(mediainfo.StreamGeneral, 0, "FileSize")),
|
||||
Duration: duration,
|
||||
Container: OrNull(mi.Parameter(mediainfo.StreamGeneral, 0, "Format")),
|
||||
Videos: Map(make([]Video, ParseUint(mi.Parameter(mediainfo.StreamVideo, 0, "StreamCount"))), func(_ Video, i int) Video {
|
||||
Size: ParseUint64(mi.Format.Size),
|
||||
Duration: float32(mi.Format.DurationSeconds),
|
||||
Container: OrNull(mi.Format.FormatName),
|
||||
Videos: MapStream(mi.Streams, ffprobe.StreamVideo, func(stream *ffprobe.Stream, i uint32) Video {
|
||||
return Video{
|
||||
Codec: mi.Parameter(mediainfo.StreamVideo, i, "Format"),
|
||||
MimeCodec: GetMimeCodec(mi, mediainfo.StreamVideo, i),
|
||||
Language: OrNull(mi.Parameter(mediainfo.StreamVideo, i, "Language")),
|
||||
Quality: QualityFromHeight(ParseUint(mi.Parameter(mediainfo.StreamVideo, i, "Height"))),
|
||||
Width: ParseUint(mi.Parameter(mediainfo.StreamVideo, i, "Width")),
|
||||
Height: ParseUint(mi.Parameter(mediainfo.StreamVideo, i, "Height")),
|
||||
Bitrate: ParseUint(
|
||||
cmp.Or(
|
||||
mi.Parameter(mediainfo.StreamVideo, i, "BitRate"),
|
||||
mi.Parameter(mediainfo.StreamVideo, i, "OverallBitRate"),
|
||||
mi.Parameter(mediainfo.StreamVideo, i, "BitRate_Nominal"),
|
||||
),
|
||||
),
|
||||
Codec: stream.CodecName,
|
||||
MimeCodec: GetMimeCodec(stream),
|
||||
Language: OrNull(stream.Tags.Language),
|
||||
Quality: QualityFromHeight(uint32(stream.Height)),
|
||||
Width: uint32(stream.Width),
|
||||
Height: uint32(stream.Height),
|
||||
Bitrate: ParseUint(stream.BitRate),
|
||||
}
|
||||
}),
|
||||
Audios: Map(make([]Audio, ParseUint(mi.Parameter(mediainfo.StreamAudio, 0, "StreamCount"))), func(_ Audio, i int) Audio {
|
||||
Audios: MapStream(mi.Streams, ffprobe.StreamAudio, func(stream *ffprobe.Stream, i uint32) Audio {
|
||||
return Audio{
|
||||
Index: uint32(i),
|
||||
Title: OrNull(mi.Parameter(mediainfo.StreamAudio, i, "Title")),
|
||||
Language: OrNull(mi.Parameter(mediainfo.StreamAudio, i, "Language")),
|
||||
Codec: mi.Parameter(mediainfo.StreamAudio, i, "Format"),
|
||||
MimeCodec: GetMimeCodec(mi, mediainfo.StreamAudio, i),
|
||||
IsDefault: mi.Parameter(mediainfo.StreamAudio, i, "Default") == "Yes",
|
||||
IsForced: mi.Parameter(mediainfo.StreamAudio, i, "Forced") == "Yes",
|
||||
Index: i,
|
||||
Title: OrNull(stream.Tags.Title),
|
||||
Language: OrNull(stream.Tags.Language),
|
||||
Codec: stream.CodecName,
|
||||
MimeCodec: GetMimeCodec(stream),
|
||||
IsDefault: stream.Disposition.Default != 0,
|
||||
IsForced: stream.Disposition.Forced != 0,
|
||||
}
|
||||
}),
|
||||
Subtitles: Map(make([]Subtitle, ParseUint(mi.Parameter(mediainfo.StreamText, 0, "StreamCount"))), func(_ Subtitle, i int) Subtitle {
|
||||
format := strings.ToLower(mi.Parameter(mediainfo.StreamText, i, "Format"))
|
||||
if format == "utf-8" {
|
||||
format = "subrip"
|
||||
}
|
||||
extension := OrNull(SubtitleExtensions[format])
|
||||
Subtitles: MapStream(mi.Streams, ffprobe.StreamSubtitle, func(stream *ffprobe.Stream, i uint32) Subtitle {
|
||||
extension := OrNull(SubtitleExtensions[stream.CodecName])
|
||||
var link *string
|
||||
if extension != nil {
|
||||
x := fmt.Sprintf("%s/%s/subtitle/%d.%s", Settings.RoutePrefix, base64.StdEncoding.EncodeToString([]byte(path)), i, *extension)
|
||||
@ -295,21 +285,26 @@ func getInfo(path string) (*MediaInfo, error) {
|
||||
}
|
||||
return Subtitle{
|
||||
Index: uint32(i),
|
||||
Title: OrNull(mi.Parameter(mediainfo.StreamText, i, "Title")),
|
||||
Language: OrNull(mi.Parameter(mediainfo.StreamText, i, "Language")),
|
||||
Codec: format,
|
||||
Title: OrNull(stream.Tags.Title),
|
||||
Language: OrNull(stream.Tags.Language),
|
||||
Codec: stream.CodecName,
|
||||
Extension: extension,
|
||||
IsDefault: mi.Parameter(mediainfo.StreamText, i, "Default") == "Yes",
|
||||
IsForced: mi.Parameter(mediainfo.StreamText, i, "Forced") == "Yes",
|
||||
IsDefault: stream.Disposition.Default != 0,
|
||||
IsForced: stream.Disposition.Forced != 0,
|
||||
Link: link,
|
||||
}
|
||||
}),
|
||||
Chapters: getChapters(chapters_begin, chapters_end, mi, duration),
|
||||
Fonts: Map(
|
||||
attachments,
|
||||
func(font string, _ int) string {
|
||||
return fmt.Sprintf("%s/%s/attachment/%s", Settings.RoutePrefix, base64.StdEncoding.EncodeToString([]byte(path)), font)
|
||||
}),
|
||||
Chapters: Map(mi.Chapters, func(c *ffprobe.Chapter, _ int) Chapter {
|
||||
return Chapter{
|
||||
Name: c.Title(),
|
||||
StartTime: float32(c.StartTimeSeconds),
|
||||
EndTime: float32(c.EndTimeSeconds),
|
||||
}
|
||||
}),
|
||||
Fonts: MapStream(mi.Streams, ffprobe.StreamAttachment, func(stream *ffprobe.Stream, i uint32) string {
|
||||
font, _ := stream.TagList.GetString("filename")
|
||||
return fmt.Sprintf("%s/%s/attachment/%s", Settings.RoutePrefix, base64.StdEncoding.EncodeToString([]byte(path)), font)
|
||||
}),
|
||||
}
|
||||
var codecs []string
|
||||
if len(ret.Videos) > 0 && ret.Videos[0].MimeCodec != nil {
|
||||
@ -334,40 +329,3 @@ func getInfo(path string) (*MediaInfo, error) {
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func chapterTimeIsValid(chapterTime string) bool {
|
||||
return len(chapterTime) > 0 && unicode.IsDigit(rune(chapterTime[0]))
|
||||
}
|
||||
|
||||
func getChapters(chapters_begin uint32, chapters_end uint32, mi *mediainfo.File, duration float32) []Chapter {
|
||||
chapterCount := max(chapters_end-chapters_begin, 0)
|
||||
chapterIterationCount := chapterCount
|
||||
chapters := make([]Chapter, chapterCount)
|
||||
chapterIndex := 0
|
||||
|
||||
for i := 0; i < int(chapterIterationCount); i++ {
|
||||
rawStartTime := mi.GetI(mediainfo.StreamMenu, 0, int(chapters_begin)+i, mediainfo.InfoName)
|
||||
rawEndTime := mi.GetI(mediainfo.StreamMenu, 0, int(chapters_begin)+i+1, mediainfo.InfoName)
|
||||
// If true, this "chapter" is invalid. We skip it
|
||||
if !chapterTimeIsValid(rawStartTime) {
|
||||
chapterIterationCount = chapterIterationCount + 1
|
||||
continue
|
||||
}
|
||||
var endTime float32
|
||||
// If this fails, we probably are at the end of the video
|
||||
// Since there would be no following chapter,
|
||||
// we defacto set the end time to the end of the video (i.e. its duration)
|
||||
if chapterTimeIsValid(rawEndTime) {
|
||||
endTime = ParseTime(rawEndTime)
|
||||
} else {
|
||||
endTime = duration
|
||||
}
|
||||
chapters[chapterIndex] = Chapter{
|
||||
StartTime: ParseTime(rawStartTime),
|
||||
EndTime: endTime,
|
||||
Name: mi.GetI(mediainfo.StreamMenu, 0, int(chapters_begin)+i, mediainfo.InfoText),
|
||||
}
|
||||
chapterIndex++
|
||||
}
|
||||
return chapters
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user