mirror of
https://github.com/zoriya/Kyoo.git
synced 2026-04-24 18:10:06 -04:00
Fix intro/outro detection
This commit is contained in:
parent
b71d6ffec3
commit
df0c2ced13
@ -194,8 +194,9 @@ export const videosMetadata = new Elysia({
|
||||
)
|
||||
.get(
|
||||
":id/prepare",
|
||||
async ({ params: { id }, headers: { authorization } }) => {
|
||||
await prepareVideo(id, authorization!);
|
||||
async ({ params: { id }, headers: { authorization }, status }) => {
|
||||
const ret = await prepareVideo(id, authorization!);
|
||||
if (ret) return status(ret.status, ret);
|
||||
},
|
||||
{
|
||||
detail: { description: "Prepare a video for playback" },
|
||||
@ -219,7 +220,6 @@ export const videosMetadata = new Elysia({
|
||||
);
|
||||
|
||||
export const prepareVideo = async (slug: string, auth: string) => {
|
||||
logger.info("Preparing next video {slug}", { slug });
|
||||
const [vid] = await db
|
||||
.select({ path: videos.path, show: entries.showPk, order: entries.order })
|
||||
.from(videos)
|
||||
@ -228,6 +228,13 @@ export const prepareVideo = async (slug: string, auth: string) => {
|
||||
.where(eq(entryVideoJoin.slug, slug))
|
||||
.limit(1);
|
||||
|
||||
if (!vid) {
|
||||
return {
|
||||
status: 404,
|
||||
message: `No video found with slug ${slug}`,
|
||||
} as const;
|
||||
}
|
||||
|
||||
const related = vid.show
|
||||
? await db
|
||||
.select({ order: entries.order, path: videos.path })
|
||||
@ -238,6 +245,14 @@ export const prepareVideo = async (slug: string, auth: string) => {
|
||||
.orderBy(entries.order)
|
||||
: [];
|
||||
const idx = related.findIndex((x) => x.order === vid.order);
|
||||
const near = [related[idx - 1], related[idx + 1]]
|
||||
.filter((x) => x)
|
||||
.map((x) => x.path);
|
||||
|
||||
logger.info("Preparing next video {slug} (near episodes: {near})", {
|
||||
slug,
|
||||
near,
|
||||
});
|
||||
|
||||
const path = Buffer.from(vid.path, "utf8").toString("base64url");
|
||||
await fetch(
|
||||
@ -252,9 +267,7 @@ export const prepareVideo = async (slug: string, auth: string) => {
|
||||
},
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
nearEpisodes: [related[idx - 1], related[idx + 1]]
|
||||
.filter((x) => x)
|
||||
.map((x) => x.path),
|
||||
nearEpisodes: near,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@ -97,7 +97,7 @@ export const Controls = ({
|
||||
<SkipChapterButton
|
||||
player={player}
|
||||
chapters={chapters}
|
||||
isVisible={hover && controlsVisible}
|
||||
isVisible={controlsVisible}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { useEvent, type VideoPlayer } from "react-native-video";
|
||||
import type { Chapter } from "~/models";
|
||||
import { Button } from "~/primitives";
|
||||
import { cn } from "~/utils";
|
||||
|
||||
export const SkipChapterButton = ({
|
||||
player,
|
||||
@ -32,7 +33,10 @@ export const SkipChapterButton = ({
|
||||
<Button
|
||||
text={t(`player.skip-${chapter.type}`)}
|
||||
onPress={() => player.seekTo(chapter.endTime)}
|
||||
className="pointer-events-box-none absolute top-safe right-safe z-20 border-slate-200 bg-slate-900/70 p-4 px-4 py-2"
|
||||
className={cn(
|
||||
"absolute right-safe bottom-2/10 m-8",
|
||||
"z-20 bg-slate-900/70 px-4 py-2",
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -155,11 +155,11 @@ func (s *MetadataService) matchByOverlap(
|
||||
slog.WarnContext(ctx, "failed to store fingerprint", "path", otherInfo.Path, "err", err)
|
||||
}
|
||||
|
||||
intros, err := FpFindOverlap(fingerprint.Start, otherPrint.Start)
|
||||
intros, err := FpFindOverlap(ctx, fingerprint.Start, otherPrint.Start)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find intro overlaps: %w", err)
|
||||
}
|
||||
credits, err := FpFindOverlap(fingerprint.End, otherPrint.End)
|
||||
credits, err := FpFindOverlap(ctx, fingerprint.End, otherPrint.End)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find credit overlaps: %w", err)
|
||||
}
|
||||
@ -178,6 +178,7 @@ func (s *MetadataService) matchByOverlap(
|
||||
continue
|
||||
}
|
||||
|
||||
slog.InfoContext(ctx, "Identified intro", "start", intro.StartFirst, "duration", intro.Duration)
|
||||
candidates = append(candidates, Chapter{
|
||||
Id: info.Id,
|
||||
StartTime: float32(intro.StartFirst),
|
||||
@ -189,9 +190,8 @@ func (s *MetadataService) matchByOverlap(
|
||||
})
|
||||
}
|
||||
|
||||
endOffset := max(info.Duration-FpEndDuration, 0)
|
||||
for _, ov := range credits {
|
||||
segData, err := ExtractSegment(fingerprint.End, ov.StartFirst, ov.StartFirst+ov.Duration)
|
||||
for _, cred := range credits {
|
||||
segData, err := ExtractSegment(fingerprint.End, cred.StartFirst, cred.StartFirst+cred.Duration)
|
||||
if err != nil {
|
||||
slog.WarnContext(ctx, "failed to extract credits segment", "err", err)
|
||||
continue
|
||||
@ -203,14 +203,16 @@ func (s *MetadataService) matchByOverlap(
|
||||
continue
|
||||
}
|
||||
|
||||
endOffset := info.Duration - samplesToSec(len(fingerprint.End))
|
||||
slog.InfoContext(ctx, "Identified credits", "start", endOffset+cred.StartFirst, "duration", cred.Duration, "end_offset", endOffset)
|
||||
candidates = append(candidates, Chapter{
|
||||
Id: info.Id,
|
||||
StartTime: float32(endOffset + ov.StartFirst),
|
||||
EndTime: float32(endOffset + ov.StartFirst + ov.Duration),
|
||||
StartTime: float32(endOffset + cred.StartFirst),
|
||||
EndTime: float32(endOffset + cred.StartFirst + cred.Duration),
|
||||
Name: "",
|
||||
Type: Credits,
|
||||
FingerprintId: &fpId,
|
||||
MatchAccuracy: new(int32(ov.Accuracy)),
|
||||
MatchAccuracy: new(int32(cred.Accuracy)),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -24,9 +24,9 @@ type Fingerprint struct {
|
||||
}
|
||||
|
||||
func (s *MetadataService) ComputeFingerprint(ctx context.Context, info *MediaInfo) (*Fingerprint, error) {
|
||||
getRunning, set := s.fingerprintLock.Start(info.Path)
|
||||
if getRunning != nil {
|
||||
return getRunning()
|
||||
get_running, set := s.fingerprintLock.Start(info.Sha)
|
||||
if get_running != nil {
|
||||
return get_running()
|
||||
}
|
||||
|
||||
var startData string
|
||||
@ -67,7 +67,7 @@ func (s *MetadataService) ComputeFingerprint(ctx context.Context, info *MediaInf
|
||||
endFingerprint, err := computeChromaprint(
|
||||
ctx,
|
||||
info.Path,
|
||||
max(info.Duration-5*60, 0),
|
||||
max(info.Duration-FpEndDuration, 0),
|
||||
-1,
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
@ -174,12 +176,14 @@ func findMatchingRuns(fp1, fp2 []uint32, start1, start2 int) []Overlap {
|
||||
|
||||
// Handle a run that extends to the last block.
|
||||
nblocks++
|
||||
blockCorr = append(blockCorr, MatchThreshold)
|
||||
blockCorr = append(blockCorr, 0)
|
||||
|
||||
for b := range nblocks {
|
||||
if blockCorr[b] >= MatchThreshold {
|
||||
if !inRun {
|
||||
runStart = b
|
||||
}
|
||||
inRun = true
|
||||
runStart = min(runStart, b)
|
||||
continue
|
||||
}
|
||||
if !inRun {
|
||||
@ -214,9 +218,10 @@ func findMatchingRuns(fp1, fp2 []uint32, start1, start2 int) []Overlap {
|
||||
// per block using the AcoustID scoring formula.
|
||||
// 4. Find contiguous runs of high-correlation blocks that are at least
|
||||
// MinOverlapDuration long.
|
||||
func FpFindOverlap(fp1 []uint32, fp2 []uint32) ([]Overlap, error) {
|
||||
func FpFindOverlap(ctx context.Context, fp1 []uint32, fp2 []uint32) ([]Overlap, error) {
|
||||
offset := findBestOffset(fp1, fp2)
|
||||
if offset == nil {
|
||||
slog.InfoContext(ctx, "no good offset found")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user