Update media info retrival with db schema

This commit is contained in:
Zoe Roux 2024-07-25 21:50:21 +02:00
parent c5047babca
commit 5047364ad3
3 changed files with 70 additions and 36 deletions

View File

@ -8,11 +8,11 @@ create table info(
size bigint not null, size bigint not null,
duration real not null, duration real not null,
container varchar(256), container varchar(256),
fonts text[], fonts text[] not null,
ver_ffprobe integer, ver_info integer not null,
ver_extract integer, ver_extract integer not null,
ver_thumbs integer, ver_thumbs integer not null,
ver_keyframes integer ver_keyframes integer not null
); );
create table videos( create table videos(

View File

@ -20,6 +20,15 @@ import (
"gopkg.in/vansante/go-ffprobe.v2" "gopkg.in/vansante/go-ffprobe.v2"
) )
const InfoVersion = 1
type Versions struct {
Info int32 `db:"ver_info"`
Extract int32 `db:"ver_extract"`
Thumbs int32 `db:"ver_thumbs"`
Keyframes int32 `db:"ver_keyframes"`
}
type MediaInfo struct { type MediaInfo struct {
// The sha1 of the video file. // The sha1 of the video file.
Sha string `json:"sha"` Sha string `json:"sha"`
@ -28,15 +37,19 @@ type MediaInfo struct {
/// The extension currently used to store this video file /// The extension currently used to store this video file
Extension string `json:"extension"` Extension string `json:"extension"`
/// The whole mimetype (defined as the RFC 6381). ex: `video/mp4; codecs="avc1.640028, mp4a.40.2"` /// The whole mimetype (defined as the RFC 6381). ex: `video/mp4; codecs="avc1.640028, mp4a.40.2"`
MimeCodec *string `json:"mimeCodec"` MimeCodec *string `json:"mimeCodec" db:"mime_codec"`
/// The file size of the video file. /// The file size of the video file.
Size int64 `json:"size"` Size int64 `json:"size"`
/// The length of the media in seconds. /// The length of the media in seconds.
Duration float32 `json:"duration"` Duration float32 `json:"duration"`
/// The container of the video file of this episode. /// The container of the video file of this episode.
Container *string `json:"container"` Container *string `json:"container"`
/// The video codec and informations. /// Version of the metadata. This can be used to invalidate older metadata from db if the extraction code has changed.
Video *Video `json:"video"` Versions Versions
// TODO: remove this
Video *Video
/// The list of videos if there are multiples. /// The list of videos if there are multiples.
Videos []Video `json:"videos"` Videos []Video `json:"videos"`
/// The list of audio tracks. /// The list of audio tracks.
@ -50,14 +63,16 @@ type MediaInfo struct {
} }
type Video struct { type Video struct {
/// The human readable codec name. /// The index of this track on the media.
Codec string `json:"codec"` Index uint32 `json:"index" db:"idx"`
/// The codec of this stream (defined as the RFC 6381).
MimeCodec *string `json:"mimeCodec"`
/// The title of the stream. /// The title of the stream.
Title *string `json:"title"` Title *string `json:"title"`
/// The language of this stream (as a ISO-639-2 language code) /// The language of this stream (as a ISO-639-2 language code)
Language *string `json:"language"` Language *string `json:"language"`
/// The human readable codec name.
Codec string `json:"codec"`
/// The codec of this stream (defined as the RFC 6381).
MimeCodec *string `json:"mimeCodec" db:"mime_codec"`
/// The max quality of this video track. /// The max quality of this video track.
Quality Quality `json:"quality"` Quality Quality `json:"quality"`
/// The width of the video stream /// The width of the video stream
@ -70,7 +85,7 @@ type Video struct {
type Audio struct { type Audio struct {
/// The index of this track on the media. /// The index of this track on the media.
Index uint32 `json:"index"` Index uint32 `json:"index" db:"idx"`
/// The title of the stream. /// The title of the stream.
Title *string `json:"title"` Title *string `json:"title"`
/// The language of this stream (as a IETF-BCP-47 language code) /// The language of this stream (as a IETF-BCP-47 language code)
@ -78,16 +93,14 @@ type Audio struct {
/// The human readable codec name. /// The human readable codec name.
Codec string `json:"codec"` Codec string `json:"codec"`
/// The codec of this stream (defined as the RFC 6381). /// The codec of this stream (defined as the RFC 6381).
MimeCodec *string `json:"mimeCodec"` MimeCodec *string `json:"mimeCodec" db:"mime_codec"`
/// Is this stream the default one of it's type? /// Is this stream the default one of it's type?
IsDefault bool `json:"isDefault"` IsDefault bool `json:"isDefault" db:"is_default"`
/// Is this stream tagged as forced? (useful only for subtitles)
IsForced bool `json:"isForced"`
} }
type Subtitle struct { type Subtitle struct {
/// The index of this track on the media. /// The index of this track on the media.
Index uint32 `json:"index"` Index uint32 `json:"index" db:"idx"`
/// The title of the stream. /// The title of the stream.
Title *string `json:"title"` Title *string `json:"title"`
/// The language of this stream (as a IETF-BCP-47 language code) /// The language of this stream (as a IETF-BCP-47 language code)
@ -97,23 +110,38 @@ type Subtitle struct {
/// The extension for the codec. /// The extension for the codec.
Extension *string `json:"extension"` Extension *string `json:"extension"`
/// Is this stream the default one of it's type? /// Is this stream the default one of it's type?
IsDefault bool `json:"isDefault"` IsDefault bool `json:"isDefault" db:"is_default"`
/// Is this stream tagged as forced? (useful only for subtitles) /// Is this stream tagged as forced?
IsForced bool `json:"isForced"` IsForced bool `json:"isForced" db:"is_forced"`
/// Is this an external subtitle (as in stored in a different file)
IsExternal bool `json:"isExternal" db:"is_external"`
/// Where the subtitle is stored (either in library if IsExternal is true or in transcoder cache if false)
/// Null if the subtitle can't be extracted (unsupported format)
Path *string `json:"path"`
/// The link to access this subtitle. /// The link to access this subtitle.
Link *string `json:"link"` Link *string `json:"link"`
} }
type Chapter struct { type Chapter struct {
/// The start time of the chapter (in second from the start of the episode). /// The start time of the chapter (in second from the start of the episode).
StartTime float32 `json:"startTime"` StartTime float32 `json:"startTime" db:"start_time"`
/// The end time of the chapter (in second from the start of the episode). /// The end time of the chapter (in second from the start of the episode).
EndTime float32 `json:"endTime"` EndTime float32 `json:"endTime" db:"end_time"`
/// The name of this chapter. This should be a human-readable name that could be presented to the user. /// The name of this chapter. This should be a human-readable name that could be presented to the user.
Name string `json:"name"` Name string `json:"name"`
// TODO: add a type field for Opening, Credits... /// The type value is used to mark special chapters (openning/credits...)
Type ChapterType
} }
type ChapterType string
const (
Content ChapterType = "content"
Recap ChapterType = "recap"
Intro ChapterType = "intro"
Credits ChapterType = "credits"
)
func ParseFloat(str string) float32 { func ParseFloat(str string) float32 {
f, err := strconv.ParseFloat(str, 32) f, err := strconv.ParseFloat(str, 32)
if err != nil { if err != nil {
@ -209,7 +237,7 @@ func GetInfo(path string, sha string) (*MediaInfo, error) {
} }
var val *MediaInfo var val *MediaInfo
val, err = getInfo(path) val, err = RetriveMediaInfo(path, sha)
if err == nil { if err == nil {
*mi.info = *val *mi.info = *val
mi.info.Sha = sha mi.info.Sha = sha
@ -247,7 +275,7 @@ func saveInfo[T any](save_path string, mi *T) error {
return os.WriteFile(save_path, content, 0o644) return os.WriteFile(save_path, content, 0o644)
} }
func getInfo(path string) (*MediaInfo, error) { func RetriveMediaInfo(path string, sha string) (*MediaInfo, error) {
defer printExecTime("mediainfo for %s", path)() defer printExecTime("mediainfo for %s", path)()
ctx, cancelFn := context.WithTimeout(context.Background(), 30*time.Second) ctx, cancelFn := context.WithTimeout(context.Background(), 30*time.Second)
@ -259,15 +287,23 @@ func getInfo(path string) (*MediaInfo, error) {
} }
ret := MediaInfo{ ret := MediaInfo{
Sha: sha,
Path: path, Path: path,
// Remove leading . // Remove leading .
Extension: filepath.Ext(path)[1:], Extension: filepath.Ext(path)[1:],
Size: ParseInt64(mi.Format.Size), Size: ParseInt64(mi.Format.Size),
Duration: float32(mi.Format.DurationSeconds), Duration: float32(mi.Format.DurationSeconds),
Container: OrNull(mi.Format.FormatName), Container: OrNull(mi.Format.FormatName),
Versions: Versions{
Info: InfoVersion,
Extract: 0,
Thumbs: 0,
Keyframes: 0,
},
Videos: MapStream(mi.Streams, ffprobe.StreamVideo, func(stream *ffprobe.Stream, i uint32) Video { Videos: MapStream(mi.Streams, ffprobe.StreamVideo, func(stream *ffprobe.Stream, i uint32) Video {
lang, _ := language.Parse(stream.Tags.Language) lang, _ := language.Parse(stream.Tags.Language)
return Video{ return Video{
Index: i,
Codec: stream.CodecName, Codec: stream.CodecName,
MimeCodec: GetMimeCodec(stream), MimeCodec: GetMimeCodec(stream),
Title: OrNull(stream.Tags.Title), Title: OrNull(stream.Tags.Title),
@ -289,15 +325,15 @@ func getInfo(path string) (*MediaInfo, error) {
Codec: stream.CodecName, Codec: stream.CodecName,
MimeCodec: GetMimeCodec(stream), MimeCodec: GetMimeCodec(stream),
IsDefault: stream.Disposition.Default != 0, IsDefault: stream.Disposition.Default != 0,
IsForced: stream.Disposition.Forced != 0,
} }
}), }),
Subtitles: MapStream(mi.Streams, ffprobe.StreamSubtitle, func(stream *ffprobe.Stream, i uint32) Subtitle { Subtitles: MapStream(mi.Streams, ffprobe.StreamSubtitle, func(stream *ffprobe.Stream, i uint32) Subtitle {
extension := OrNull(SubtitleExtensions[stream.CodecName]) extension := OrNull(SubtitleExtensions[stream.CodecName])
var link *string var link string
var path string
if extension != nil { if extension != nil {
x := fmt.Sprintf("%s/%s/subtitle/%d.%s", Settings.RoutePrefix, base64.StdEncoding.EncodeToString([]byte(path)), i, *extension) link = fmt.Sprintf("%s/%s/subtitle/%d.%s", Settings.RoutePrefix, base64.StdEncoding.EncodeToString([]byte(path)), i, *extension)
link = &x path = fmt.Sprintf("%s/%s/sub/%d.%s", Settings.Metadata, sha, i, extension)
} }
lang, _ := language.Parse(stream.Tags.Language) lang, _ := language.Parse(stream.Tags.Language)
return Subtitle{ return Subtitle{
@ -308,7 +344,8 @@ func getInfo(path string) (*MediaInfo, error) {
Extension: extension, Extension: extension,
IsDefault: stream.Disposition.Default != 0, IsDefault: stream.Disposition.Default != 0,
IsForced: stream.Disposition.Forced != 0, IsForced: stream.Disposition.Forced != 0,
Link: link, Link: &link,
Path: &path,
} }
}), }),
Chapters: Map(mi.Chapters, func(c *ffprobe.Chapter, _ int) Chapter { Chapters: Map(mi.Chapters, func(c *ffprobe.Chapter, _ int) Chapter {
@ -316,6 +353,8 @@ func getInfo(path string) (*MediaInfo, error) {
Name: c.Title(), Name: c.Title(),
StartTime: float32(c.StartTimeSeconds), StartTime: float32(c.StartTimeSeconds),
EndTime: float32(c.EndTimeSeconds), EndTime: float32(c.EndTimeSeconds),
// TODO: detect content type
Type: Content,
} }
}), }),
Fonts: MapStream(mi.Streams, ffprobe.StreamAttachment, func(stream *ffprobe.Stream, i uint32) string { Fonts: MapStream(mi.Streams, ffprobe.StreamAttachment, func(stream *ffprobe.Stream, i uint32) string {

View File

@ -14,11 +14,6 @@ import (
"github.com/zoriya/kyoo/transcoder/src" "github.com/zoriya/kyoo/transcoder/src"
) )
// Encode the version in the hash path to update cached values.
// Older versions won't be deleted (needed to allow multiples versions of the transcoder to run at the same time)
// If the version changes a lot, we might want to automatically delete older versions.
var version = "v3-"
func GetPath(c echo.Context) (string, string, error) { func GetPath(c echo.Context) (string, string, error) {
key := c.Param("path") key := c.Param("path")
if key == "" { if key == "" {