Create types/routes for fingerprinting

This commit is contained in:
Zoe Roux 2026-04-13 10:28:02 +02:00
parent fbb995a3a6
commit f09728a993
No known key found for this signature in database
5 changed files with 84 additions and 20 deletions

View File

@ -0,0 +1,9 @@
begin;
alter table gocoder.chapters drop column match_accuracy;
alter table gocoder.chapters drop column fingerprint_id;
drop table gocoder.chapterprints;
drop table gocoder.fingerprints;
alter table gocoder.info drop column ver_fingerprint;
commit;

View File

@ -0,0 +1,19 @@
begin;
alter table gocoder.info add column ver_fingerprint integer not null default 0;
create table gocoder.fingerprints(
id integer not null primary key references gocoder.info(id) on delete cascade,
start_data text not null,
end_data text not null
);
create table gocoder.chapterprints(
id serial primary key,
data text not null
);
alter table gocoder.chapters add column fingerprint_id integer references gocoder.chapterprints(id) on delete set null;
alter table gocoder.chapters add column match_accuracy integer;
commit;

View File

@ -23,7 +23,7 @@ func RegisterMetadataHandlers(e *echo.Group, metadata *src.MetadataService) {
h := mhandler{metadata}
e.GET("/:path/info", h.GetInfo)
e.GET("/:path/prepare", h.Prepare)
e.POST("/:path/prepare", h.Prepare)
e.GET("/:path/subtitle/:name", h.GetSubtitle)
e.GET("/:path/attachment/:name", h.GetAttachment)
e.GET("/:path/thumbnails.png", h.GetThumbnails)
@ -62,10 +62,11 @@ func (h *mhandler) GetInfo(c *echo.Context) error {
Container: nil,
MimeCodec: nil,
Versions: src.Versions{
Info: -1,
Extract: 0,
Thumbs: 0,
Keyframes: 0,
Info: -1,
Extract: 0,
Thumbs: 0,
Keyframes: 0,
Fingerprint: 0,
},
Videos: make([]src.Video, 0),
Audios: make([]src.Audio, 0),
@ -77,22 +78,34 @@ func (h *mhandler) GetInfo(c *echo.Context) error {
return c.JSON(http.StatusOK, ret)
}
type PrepareRequest struct {
// File path of the previous/next episodes (for audio fingerprinting).
NearEpisodes *string `json:"nearEpisodes"`
}
// @Summary Prepare metadata
//
// @Description Starts metadata preparation in background (info, extract, thumbs, keyframes).
// @Description Starts metadata preparation in background (info, extract, thumbs, keyframes, chapter identification).
//
// @Tags metadata
// @Param path path string true "Base64 of a video's path" format(base64) example(L3ZpZGVvL2J1YmJsZS5ta3YK)
// @Param body body PrepareRequest false "Adjacent episode paths for chapter detection"
//
// @Success 202 "Preparation started"
// @Router /:path/prepare [get]
// @Router /:path/prepare [post]
func (h *mhandler) Prepare(c *echo.Context) error {
path, sha, err := getPath(c)
if err != nil {
return err
}
go func(path string, sha string) {
var req PrepareRequest
err = c.Bind(&req)
if err != nil {
return echo.NewHTTPError(http.StatusUnprocessableEntity, err.Error())
}
go func() {
bgCtx := context.Background()
info, err := h.metadata.GetMetadata(bgCtx, path, sha)
@ -113,7 +126,9 @@ func (h *mhandler) Prepare(c *echo.Context) error {
fmt.Printf("failed to extract audio keyframes for %s (stream %d): %v\n", path, audio.Index, err)
}
}
}(path, sha)
h.metadata.IdentifyChapters(bgCtx, info, req.NearEpisodes)
}()
return c.NoContent(http.StatusAccepted)
}

View File

@ -20,10 +20,11 @@ import (
const InfoVersion = 4
type Versions struct {
Info int32 `json:"info" db:"ver_info"`
Extract int32 `json:"extract" db:"ver_extract"`
Thumbs int32 `json:"thumbs" db:"ver_thumbs"`
Keyframes int32 `json:"keyframes" db:"ver_keyframes"`
Info int32 `json:"info" db:"ver_info"`
Extract int32 `json:"extract" db:"ver_extract"`
Thumbs int32 `json:"thumbs" db:"ver_thumbs"`
Keyframes int32 `json:"keyframes" db:"ver_keyframes"`
Fingerprint int32 `json:"fingerprint" db:"ver_fingerprint"`
}
type MediaInfo struct {
@ -149,6 +150,10 @@ type Chapter struct {
Name string `json:"name" db:"name"`
/// The type value is used to mark special chapters (openning/credits...)
Type ChapterType `json:"type" db:"type"`
/// Reference to the chapterprint used for fingerprint matching.
FingerprintId *int32 `json:"-" db:"fingerprint_id"`
/// Accuracy of the fingerprint match (0-100).
MatchAccuracy *int32 `json:"matchAccuracy,omitempty" db:"match_accuracy"`
}
type ChapterType string
@ -255,10 +260,11 @@ func RetriveMediaInfo(path string, sha string) (*MediaInfo, error) {
Duration: mi.Format.DurationSeconds,
Container: OrNull(mi.Format.FormatName),
Versions: Versions{
Info: InfoVersion,
Extract: 0,
Thumbs: 0,
Keyframes: 0,
Info: InfoVersion,
Extract: 0,
Thumbs: 0,
Keyframes: 0,
Fingerprint: 0,
},
Videos: MapStream(mi.Streams, ffprobe.StreamVideo, func(stream *ffprobe.Stream, i uint32) Video {
lang, _ := language.Parse(stream.Tags.Language)

View File

@ -180,6 +180,19 @@ func (s *MetadataService) GetMetadata(ctx context.Context, path string, sha stri
}
}
if ret.Versions.Fingerprint < FingerprintVersion && ret.Versions.Fingerprint != 0 {
tx, err := s.Database.Begin(bgCtx)
if err != nil {
return nil, err
}
tx.Exec(bgCtx, `delete from gocoder.fingerprints where id = $1`, ret.Id)
tx.Exec(bgCtx, `update gocoder.info set ver_fingerprint = 0 where id = $1`, ret.Id)
err = tx.Commit(bgCtx)
if err != nil {
fmt.Printf("error deleting old fingerprints from database: %v", err)
}
}
return ret, nil
}
@ -192,7 +205,8 @@ func (s *MetadataService) getMetadata(ctx context.Context, path string, sha stri
'info', i.ver_info,
'extract', i.ver_extract,
'thumbs', i.ver_thumbs,
'keyframes', i.ver_keyframes
'keyframes', i.ver_keyframes,
'fingerprint', i.ver_fingerprint
) as versions
from gocoder.info as i
where i.sha=$1 limit 1`,
@ -286,13 +300,14 @@ func (s *MetadataService) storeFreshMetadata(ctx context.Context, path string, s
err = tx.QueryRow(ctx,
`
insert into gocoder.info(sha, path, extension, mime_codec, size, duration, container,
fonts, ver_info, ver_extract, ver_thumbs, ver_keyframes)
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
fonts, ver_info, ver_extract, ver_thumbs, ver_keyframes, ver_fingerprint)
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
returning id
`,
// on conflict do not update versions of extract/thumbs/keyframes
ret.Sha, ret.Path, ret.Extension, ret.MimeCodec, ret.Size, ret.Duration, ret.Container,
ret.Fonts, ret.Versions.Info, ret.Versions.Extract, ret.Versions.Thumbs, ret.Versions.Keyframes,
ret.Versions.Fingerprint,
).Scan(&ret.Id)
if err != nil {
return set(ret, fmt.Errorf("failed to insert info: %w", err))