- Wire introdb marker fetching into playback and Jellyfin compat - Persist and expose recap/preview markers alongside intro and credits - Add new playback/profile settings for recap and preview behavior
444 lines
16 KiB
Go
444 lines
16 KiB
Go
package models
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// MediaFolder represents a row in the media_folders table.
|
|
type MediaFolder struct {
|
|
ID int
|
|
Paths []string // from media_folder_paths child table
|
|
Type string // movies, series, mixed
|
|
Name string
|
|
Enabled bool
|
|
MetadataLanguage string // ISO 639-1 code (e.g. "en", "ja")
|
|
ChapterThumbnailsEnabled bool
|
|
IntroDetectionEnabled bool
|
|
PosterPath string // S3 key for library poster image
|
|
LastScannedAt *time.Time // nullable
|
|
ScanWarningCode *string
|
|
ScanWarningMessage *string
|
|
ScanWarningAt *time.Time
|
|
AllowEmptyCleanupOnce bool
|
|
SortOrder int
|
|
}
|
|
|
|
// MediaFile represents a row in the media_files table.
|
|
type MediaFile struct {
|
|
ID int
|
|
ContentID string // Sonyflake ID (nullable until matched)
|
|
EpisodeID string // FK to episodes.content_id (nullable)
|
|
SeasonNumber int // parsed from filename (nullable)
|
|
EpisodeNumber int // parsed from filename (nullable)
|
|
MediaFolderID int
|
|
CanonicalRootPath string
|
|
ObservedRootPath string
|
|
ContentGroupKey string
|
|
GroupKeyVersion int
|
|
BaseTitle string
|
|
BaseYear int
|
|
BaseType string
|
|
IdentityConfidence string
|
|
IdentityJSON []byte
|
|
FilePath string
|
|
FileSize int64
|
|
FileModifiedAt *time.Time
|
|
FileHash string // OSHash (16-char hex)
|
|
CodecVideo string // h264, hevc, av1
|
|
CodecAudio string // aac, opus, flac
|
|
Resolution string // 1080p, 2160p
|
|
AudioChannels int
|
|
HDR bool
|
|
Container string // mkv, mp4
|
|
Duration int // seconds
|
|
Bitrate int // kbps
|
|
VideoTracks []VideoTrack // JSONB
|
|
AudioTracks []AudioTrack // JSONB
|
|
SubtitleTracks []SubtitleTrack // JSONB
|
|
ExternalSubtitles []ExternalSubtitle // JSONB
|
|
Chapters []MediaChapter // JSONB; nil means not yet probed for chapters
|
|
ChapterThumbnailRetryAfter *time.Time
|
|
ChapterThumbnailFailureCount int
|
|
ChapterThumbnailLastError string
|
|
IntroStart *float64
|
|
IntroEnd *float64
|
|
CreditsStart *float64
|
|
CreditsEnd *float64
|
|
RecapStart *float64
|
|
RecapEnd *float64
|
|
PreviewStart *float64
|
|
PreviewEnd *float64
|
|
MarkersSource *string
|
|
MarkersConfidence *float64
|
|
IntroMarkersSource *string
|
|
IntroMarkersProvider *string
|
|
IntroMarkersConfidence *float64
|
|
IntroMarkersAlgorithm *string
|
|
IntroMarkersDetectedAt *time.Time
|
|
CreditsMarkersSource *string
|
|
CreditsMarkersProvider *string
|
|
CreditsMarkersConfidence *float64
|
|
CreditsMarkersAlgorithm *string
|
|
CreditsMarkersDetectedAt *time.Time
|
|
RecapMarkersSource *string
|
|
RecapMarkersProvider *string
|
|
RecapMarkersConfidence *float64
|
|
RecapMarkersAlgorithm *string
|
|
RecapMarkersDetectedAt *time.Time
|
|
PreviewMarkersSource *string
|
|
PreviewMarkersProvider *string
|
|
PreviewMarkersConfidence *float64
|
|
PreviewMarkersAlgorithm *string
|
|
PreviewMarkersDetectedAt *time.Time
|
|
EditionRaw string
|
|
EditionKey string
|
|
EditionConfidence *float64
|
|
EditionSource string
|
|
PresentationKind string
|
|
PresentationGroupKey string
|
|
PresentationPartIndex int
|
|
PresentationPartTotal int
|
|
MultiEpisodeStart int
|
|
MultiEpisodeEnd int
|
|
ProbeSource string // arrs, local
|
|
ProbeUpdatedAt *time.Time
|
|
MatchAttemptedAt *time.Time
|
|
MissingSince *time.Time
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
// MediaChapter represents a single media chapter derived from embedded file metadata.
|
|
type MediaChapter struct {
|
|
Index int `json:"index"`
|
|
Title string `json:"title"`
|
|
StartSeconds float64 `json:"start_seconds"`
|
|
EndSeconds float64 `json:"end_seconds"`
|
|
Source string `json:"source"`
|
|
ThumbnailPath string `json:"thumbnail_path,omitempty"`
|
|
ThumbnailThumbhash string `json:"thumbnail_thumbhash,omitempty"`
|
|
ThumbnailRetryAfter *time.Time `json:"thumbnail_retry_after,omitempty"`
|
|
ThumbnailFailedAt *time.Time `json:"thumbnail_failed_at,omitempty"`
|
|
ThumbnailLastError string `json:"thumbnail_last_error,omitempty"`
|
|
}
|
|
|
|
// OverlaySummary is the compact media badge payload shared across API surfaces.
|
|
type OverlaySummary struct {
|
|
Resolution string `json:"resolution,omitempty"`
|
|
HDR string `json:"hdr,omitempty"`
|
|
Audio string `json:"audio,omitempty"`
|
|
AudioChannels string `json:"audio_channels,omitempty"` // "Stereo", "5.1", "7.1"
|
|
VideoCodec string `json:"video_codec,omitempty"` // "H.264", "H.265", "AV1"
|
|
Container string `json:"container,omitempty"` // "MKV", "MP4"
|
|
AspectRatio string `json:"aspect_ratio,omitempty"` // "16:9", "2.39:1"
|
|
ReleaseType string `json:"release_type,omitempty"`
|
|
Edition string `json:"edition,omitempty"`
|
|
MultiAudio bool `json:"multi_audio,omitempty"` // ≥2 distinct audio languages
|
|
MultiSub bool `json:"multi_sub,omitempty"` // ≥1 subtitle track (embedded or external)
|
|
}
|
|
|
|
// VideoTrack represents a probed video stream stored as JSONB.
|
|
type VideoTrack struct {
|
|
Title string `json:"title,omitempty"`
|
|
Codec string `json:"codec,omitempty"`
|
|
DolbyVision string `json:"dolby_vision,omitempty"`
|
|
Profile string `json:"profile,omitempty"`
|
|
Level int `json:"level,omitempty"`
|
|
Width int `json:"width,omitempty"`
|
|
Height int `json:"height,omitempty"`
|
|
AspectRatio string `json:"aspect_ratio,omitempty"`
|
|
Interlaced bool `json:"interlaced"`
|
|
FrameRate string `json:"frame_rate,omitempty"`
|
|
Bitrate int `json:"bitrate,omitempty"`
|
|
VideoRange string `json:"video_range,omitempty"`
|
|
ColorPrimaries string `json:"color_primaries,omitempty"`
|
|
ColorSpace string `json:"color_space,omitempty"`
|
|
ColorTransfer string `json:"color_transfer,omitempty"`
|
|
BitDepth int `json:"bit_depth,omitempty"`
|
|
PixelFormat string `json:"pixel_format,omitempty"`
|
|
ReferenceFrames int `json:"reference_frames,omitempty"`
|
|
}
|
|
|
|
// AudioTrack represents a probed audio stream stored as JSONB.
|
|
type AudioTrack struct {
|
|
Title string `json:"title,omitempty"`
|
|
EmbeddedTitle string `json:"embedded_title,omitempty"`
|
|
Language string `json:"language,omitempty"`
|
|
Codec string `json:"codec,omitempty"`
|
|
Layout string `json:"layout,omitempty"`
|
|
Channels int `json:"channels,omitempty"`
|
|
Bitrate int `json:"bitrate,omitempty"`
|
|
SampleRate int `json:"sample_rate,omitempty"`
|
|
BitDepth int `json:"bit_depth,omitempty"`
|
|
Default bool `json:"default"`
|
|
}
|
|
|
|
// SubtitleTrack represents an embedded subtitle track stored as JSONB.
|
|
type SubtitleTrack struct {
|
|
Index int `json:"index"`
|
|
Language string `json:"language"`
|
|
Codec string `json:"codec"`
|
|
Title string `json:"title,omitempty"`
|
|
EmbeddedTitle string `json:"embedded_title,omitempty"`
|
|
Resolution string `json:"resolution,omitempty"`
|
|
Forced bool `json:"forced"`
|
|
Default bool `json:"default"`
|
|
HearingImpaired bool `json:"hearing_impaired"`
|
|
External bool `json:"external"`
|
|
FileName string `json:"file_name,omitempty"`
|
|
}
|
|
|
|
// ExternalSubtitle represents a sidecar subtitle file stored as JSONB.
|
|
type ExternalSubtitle struct {
|
|
Path string `json:"path"`
|
|
Language string `json:"language"`
|
|
Format string `json:"format"` // srt, vtt, ass, ssa, sub
|
|
Title string `json:"title,omitempty"`
|
|
EmbeddedTitle string `json:"embedded_title,omitempty"`
|
|
Resolution string `json:"resolution,omitempty"`
|
|
Forced bool `json:"forced"`
|
|
Default bool `json:"default"`
|
|
HearingImpaired bool `json:"hearing_impaired"`
|
|
}
|
|
|
|
// PersonKind identifies the role type a person has on a media item.
|
|
type PersonKind int
|
|
|
|
const (
|
|
PersonKindActor PersonKind = 1
|
|
PersonKindDirector PersonKind = 2
|
|
PersonKindWriter PersonKind = 3
|
|
PersonKindProducer PersonKind = 4
|
|
PersonKindGuestStar PersonKind = 5
|
|
PersonKindComposer PersonKind = 6
|
|
)
|
|
|
|
// String returns the Jellyfin-compatible type string for this PersonKind.
|
|
func (k PersonKind) String() string {
|
|
switch k {
|
|
case PersonKindActor:
|
|
return "Actor"
|
|
case PersonKindDirector:
|
|
return "Director"
|
|
case PersonKindWriter:
|
|
return "Writer"
|
|
case PersonKindProducer:
|
|
return "Producer"
|
|
case PersonKindGuestStar:
|
|
return "GuestStar"
|
|
case PersonKindComposer:
|
|
return "Composer"
|
|
default:
|
|
return "Unknown"
|
|
}
|
|
}
|
|
|
|
// PersonKindFromJob maps a crew job title to a PersonKind.
|
|
func PersonKindFromJob(job string) PersonKind {
|
|
switch strings.ToLower(strings.TrimSpace(job)) {
|
|
case "director":
|
|
return PersonKindDirector
|
|
case "writer", "screenplay", "story", "novel":
|
|
return PersonKindWriter
|
|
case "composer", "original music composer", "music":
|
|
return PersonKindComposer
|
|
default:
|
|
return PersonKindProducer
|
|
}
|
|
}
|
|
|
|
// Person represents a deduplicated person entity.
|
|
type Person struct {
|
|
ID int64
|
|
Name string
|
|
SortName string
|
|
Bio string
|
|
BirthDate *time.Time
|
|
DeathDate *time.Time
|
|
Birthplace string
|
|
Homepage string
|
|
PhotoPath string
|
|
PhotoThumbhash string
|
|
TmdbID string
|
|
ImdbID string
|
|
TvdbID string
|
|
PlexGUID string
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
// ItemPerson represents a person's credit on a specific media item.
|
|
type ItemPerson struct {
|
|
Person
|
|
Kind PersonKind
|
|
Character string
|
|
SortOrder int
|
|
}
|
|
|
|
// MediaItem represents a row in the media_items table.
|
|
type MediaItem struct {
|
|
ContentID string // Sonyflake ID (PK)
|
|
Type string // movie, series
|
|
Title string
|
|
SortTitle string
|
|
DefaultMetadataLanguage string
|
|
OriginalTitle string
|
|
Year int
|
|
Genres []string
|
|
ContentRating string // PG-13, TV-MA
|
|
Runtime int // minutes
|
|
Overview string
|
|
Tagline string
|
|
RatingIMDB *float64
|
|
RatingTMDB *float64
|
|
RatingRTCritic *int
|
|
RatingRTAudience *int
|
|
ImdbID string
|
|
TmdbID string
|
|
TvdbID string
|
|
PosterPath string // S3 path
|
|
PosterThumbhash string
|
|
BackdropPath string
|
|
BackdropThumbhash string
|
|
LogoPath string
|
|
MetadataS3Path string
|
|
MetadataEtag string
|
|
SeasonCount *int // series only
|
|
Studios []string
|
|
Networks []string
|
|
Countries []string
|
|
Keywords []string
|
|
OriginalLanguage string
|
|
ReleaseDate *string // ISO date, nullable
|
|
FirstAirDate *string // ISO date (series only), nullable
|
|
LastAirDate *string // ISO date (series only), nullable
|
|
AirTime *string // Series broadcast time (e.g. "20:00"), nullable
|
|
ShowStatus string // Series lifecycle: "returning", "ended", "cancelled", "in_production", or "" if unknown (series only)
|
|
People []ItemPerson
|
|
MatchedAt *time.Time
|
|
EpisodeMetadataIncomplete bool
|
|
EpisodeMetadataLastCheckedAt *time.Time
|
|
LastRefreshed *time.Time `json:"last_refreshed,omitempty"`
|
|
RefreshFailures int `json:"refresh_failures"`
|
|
LockedFields []int `json:"locked_fields"`
|
|
Status string `json:"status"` // pending, matched, unmatched
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
AddedAt *time.Time // populated by browse queries (MIN(mil.first_seen_at))
|
|
}
|
|
|
|
// Season represents a row in the seasons table.
|
|
type Season struct {
|
|
ContentID string
|
|
SeriesID string
|
|
SeasonNumber int
|
|
Title string
|
|
DefaultMetadataLanguage string
|
|
Overview string
|
|
AirDate *time.Time
|
|
PosterPath string
|
|
PosterThumbhash string
|
|
MetadataS3Path string
|
|
MetadataEtag string
|
|
MetadataSource string
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
// Episode represents a row in the episodes table.
|
|
type Episode struct {
|
|
ContentID string // episode Sonyflake ID (PK)
|
|
SeriesID string
|
|
SeasonID string // FK to seasons.content_id
|
|
SeasonNumber int
|
|
EpisodeNumber int
|
|
Title string
|
|
DefaultMetadataLanguage string
|
|
Overview string
|
|
AirDate *time.Time
|
|
Runtime int // minutes
|
|
RatingIMDB *float64
|
|
RatingTMDB *float64
|
|
ImdbID string
|
|
TmdbID string
|
|
TvdbID string
|
|
StillPath string
|
|
StillThumbhash string
|
|
MetadataS3Path string
|
|
MetadataEtag string
|
|
MetadataSource string
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
// MediaItemRoot represents a row in the media_item_roots table.
|
|
// It records which content_id owns a given canonical root path within a library folder.
|
|
type MediaItemRoot struct {
|
|
MediaFolderID int
|
|
CanonicalRootPath string
|
|
ContentID string
|
|
FirstSeenAt time.Time
|
|
LastSeenAt time.Time
|
|
}
|
|
|
|
// MediaItemGroup represents a row in the media_item_groups table.
|
|
type MediaItemGroup struct {
|
|
MediaFolderID int
|
|
GroupKeyVersion int
|
|
ContentGroupKey string
|
|
ContentID string
|
|
FirstSeenAt time.Time
|
|
LastSeenAt time.Time
|
|
}
|
|
|
|
// MediaItemLibrary represents a row in the media_item_libraries junction table.
|
|
type MediaItemLibrary struct {
|
|
ContentID string
|
|
MediaFolderID int
|
|
FirstSeenAt time.Time
|
|
}
|
|
|
|
// EpisodeLibrary represents a row in the episode_libraries junction table.
|
|
type EpisodeLibrary struct {
|
|
EpisodeID string
|
|
MediaFolderID int
|
|
FirstSeenAt time.Time
|
|
}
|
|
|
|
type MediaItemLocalization struct {
|
|
ContentID string
|
|
Language string
|
|
Title string
|
|
SortTitle string
|
|
Overview string
|
|
Tagline string
|
|
PosterPath string
|
|
PosterThumbhash string
|
|
BackdropPath string
|
|
BackdropThumbhash string
|
|
LogoPath string
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
type SeasonLocalization struct {
|
|
SeasonContentID string
|
|
Language string
|
|
Title string
|
|
Overview string
|
|
PosterPath string
|
|
PosterThumbhash string
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
type EpisodeLocalization struct {
|
|
EpisodeContentID string
|
|
Language string
|
|
Title string
|
|
Overview string
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|