itube-specs 0.0.928 → 0.0.929
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -191,7 +191,12 @@ const currentSource = computed<VideoSource>(() => {
|
|
|
191
191
|
thumbnails: props.data.vttUrl,
|
|
192
192
|
// Главы: плеер сам рисует кнопку сцен, подписи и превью из VTT.
|
|
193
193
|
// ITimestamp.duration — момент старта главы (секунды).
|
|
194
|
-
|
|
194
|
+
// `end_duration` бекенд отдаёт не всегда — без него плеер досчитывает конец сам
|
|
195
|
+
chapters: props.data.actions?.map((ts) => ({
|
|
196
|
+
start: ts.duration,
|
|
197
|
+
...(Number(ts.end_duration) > ts.duration ? { end: Number(ts.end_duration) } : {}),
|
|
198
|
+
title: ts.title,
|
|
199
|
+
})),
|
|
195
200
|
// timelines_groups → sceneGroups и hotspots → heatmap (маппинг выше).
|
|
196
201
|
sceneGroups: sceneGroups.value,
|
|
197
202
|
heatmap: heatmap.value,
|
package/package.json
CHANGED
|
@@ -39,6 +39,71 @@ describe('cleanVideoData', () => {
|
|
|
39
39
|
expect(result.thumbNum).toBe(5);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
it('таймлайны: конец периода из бекендного end_duration', () => {
|
|
43
|
+
// форма как на бою: у первой сцены группы `locations` старта нет вовсе — это ноль
|
|
44
|
+
const raw = {
|
|
45
|
+
timelines_groups: [
|
|
46
|
+
{
|
|
47
|
+
name: 'actions',
|
|
48
|
+
title: 'Scenes',
|
|
49
|
+
actions: [
|
|
50
|
+
{ title: 'oral', duration: 75, end_duration: 140 },
|
|
51
|
+
{ title: 'anal', duration: 264, end_duration: 464 },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'locations',
|
|
56
|
+
title: 'locations',
|
|
57
|
+
actions: [
|
|
58
|
+
{ title: 'bedroom', end_duration: 200 },
|
|
59
|
+
{ title: 'living_room', duration: 200, end_duration: 479 },
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
expect(cleanVideoData(raw as any).timelinesGroups).toEqual([
|
|
66
|
+
{
|
|
67
|
+
id: 'actions',
|
|
68
|
+
title: 'Scenes',
|
|
69
|
+
scenes: [
|
|
70
|
+
{ start: 75, end: 140, title: 'Oral' },
|
|
71
|
+
{ start: 264, end: 464, title: 'Anal' },
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'locations',
|
|
76
|
+
title: 'Locations',
|
|
77
|
+
scenes: [
|
|
78
|
+
{ start: 0, end: 200, title: 'Bedroom' },
|
|
79
|
+
{ start: 200, end: 479, title: 'Living room' },
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('таймлайны: битый или отсутствующий конец периода не пробрасываем', () => {
|
|
86
|
+
const raw = {
|
|
87
|
+
timelines_groups: [
|
|
88
|
+
{
|
|
89
|
+
name: 'actions',
|
|
90
|
+
title: 'Scenes',
|
|
91
|
+
actions: [
|
|
92
|
+
{ title: 'oral', duration: 75 },
|
|
93
|
+
{ title: 'anal', duration: 264, end_duration: 100 },
|
|
94
|
+
{ title: 'kiss', duration: 300, end_duration: 300 },
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
expect(cleanVideoData(raw as any).timelinesGroups?.[0].scenes).toEqual([
|
|
101
|
+
{ start: 75, title: 'Oral' },
|
|
102
|
+
{ start: 264, title: 'Anal' },
|
|
103
|
+
{ start: 300, title: 'Kiss' },
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
|
|
42
107
|
it('пустые данные → дефолты', () => {
|
|
43
108
|
const result = cleanVideoData({} as any);
|
|
44
109
|
expect(result.guid).toBe('');
|
|
@@ -6,7 +6,11 @@ const humanize = (value?: string | null): string => {
|
|
|
6
6
|
return text ? text.charAt(0).toUpperCase() + text.slice(1) : text;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
// timelines_groups ({ name, actions:[{ duration?, title }] }) →
|
|
9
|
+
// timelines_groups ({ name, actions:[{ duration?, end_duration?, title }] }) →
|
|
10
|
+
// sceneGroups ({ id, title, scenes:[{ start, end?, title }] }). `end` — бекендный конец
|
|
11
|
+
// периода: из него плеер строит «0:00 – 1:54» в меню тайм-кодов (опция
|
|
12
|
+
// `controls.sceneTimes`), а без него досчитывает конец по старту следующей сцены.
|
|
13
|
+
// Мусор не пропускаем: конец раньше старта плееру не нужен.
|
|
10
14
|
const cleanTimelineGroups = (raw: ITimelineGroup[] | null): ISceneGroup[] | null => {
|
|
11
15
|
if (!Array.isArray(raw)) return null;
|
|
12
16
|
|
|
@@ -17,10 +21,16 @@ const cleanTimelineGroups = (raw: ITimelineGroup[] | null): ISceneGroup[] | null
|
|
|
17
21
|
title: humanize(group.title),
|
|
18
22
|
scenes: group.actions
|
|
19
23
|
.filter((action) => action?.title)
|
|
20
|
-
.map((action) =>
|
|
21
|
-
start
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
.map((action) => {
|
|
25
|
+
const start = Number(action.duration) || 0;
|
|
26
|
+
const end = Number(action.end_duration) || 0;
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
start,
|
|
30
|
+
...(end > start ? { end } : {}),
|
|
31
|
+
title: humanize(action.title),
|
|
32
|
+
};
|
|
33
|
+
}),
|
|
24
34
|
}))
|
|
25
35
|
.filter((group) => group.scenes.length > 0);
|
|
26
36
|
|
package/types/video-data.d.ts
CHANGED
|
@@ -2,12 +2,16 @@ import type { IThumbUrls } from '~/types/thumbs-urls';
|
|
|
2
2
|
|
|
3
3
|
export interface ITimestamp {
|
|
4
4
|
duration: number;
|
|
5
|
+
/** Конец периода в секундах — бекендное `end_duration`, если он его отдал. */
|
|
6
|
+
end_duration?: number;
|
|
5
7
|
title: string;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
// Сырые группы таймлайна, как приходят с бека.
|
|
9
11
|
export interface ITimelineGroupAction {
|
|
10
12
|
duration?: number;
|
|
13
|
+
/** Конец периода в секундах. Бекенд отдаёт его не у каждой сцены. */
|
|
14
|
+
end_duration?: number;
|
|
11
15
|
title: string;
|
|
12
16
|
}
|
|
13
17
|
|
|
@@ -20,6 +24,8 @@ export interface ITimelineGroup {
|
|
|
20
24
|
// Очищенная форма: id (сырое имя, для иконок), человекочитаемый title, сцены со start.
|
|
21
25
|
export interface ISceneGroupScene {
|
|
22
26
|
start: number;
|
|
27
|
+
/** Конец сцены: плеер строит из него период «0:00 – 1:54» в меню тайм-кодов. */
|
|
28
|
+
end?: number;
|
|
23
29
|
title: string;
|
|
24
30
|
}
|
|
25
31
|
|