Renamed chapters to clips. Fixed Vixen trailers.
This commit is contained in:
parent
2835c66694
commit
501e764c21
|
@ -0,0 +1,89 @@
|
|||
<template>
|
||||
<ul class="clips nolist">
|
||||
<li
|
||||
v-for="clip in clips"
|
||||
:key="`clip-${clip.id}`"
|
||||
class="clip"
|
||||
>
|
||||
<a
|
||||
v-if="clip.poster"
|
||||
:href="`/media/${clip.poster.path}`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="clip-poster-link"
|
||||
>
|
||||
<img
|
||||
:src="`/media/${clip.poster.thumbnail}`"
|
||||
class="clip-poster"
|
||||
>
|
||||
</a>
|
||||
|
||||
<div class="clip-info">
|
||||
<div class="clip-header">
|
||||
<h3 class="clip-title">{{ clip.title }}</h3>
|
||||
<span
|
||||
v-if="clip.duration"
|
||||
class="clip-duration"
|
||||
>{{ formatDuration(clip.duration) }}</span>
|
||||
</div>
|
||||
|
||||
<p
|
||||
v-if="clip.description"
|
||||
class="clip-description"
|
||||
>{{ clip.description }}</p>
|
||||
|
||||
<Tags :tags="clip.tags" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Tags from './tags.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Tags,
|
||||
},
|
||||
props: {
|
||||
clips: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.clip {
|
||||
display: grid;
|
||||
grid-template-columns: .25fr .75fr;
|
||||
background: var(--background);
|
||||
box-shadow: 0 0 3px var(--shadow-weak);
|
||||
margin: 0 0 .5rem 0;
|
||||
}
|
||||
|
||||
.clip-poster {
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.clip-info {
|
||||
flex-grow: 1;
|
||||
padding: 1rem 1rem .5rem 1rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.clip-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.clip-title {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
|
@ -105,20 +105,20 @@ function sfw() {
|
|||
}
|
||||
|
||||
function photos() {
|
||||
const photosWithChapterPosters = (this.release.photos || []).concat(this.release.chapters ? this.release.chapters.map(chapter => chapter.poster) : []);
|
||||
const photosWithClipPosters = (this.release.photos || []).concat(this.release.clips ? this.release.clips.map(clip => clip.poster) : []);
|
||||
|
||||
if (this.release.trailer || this.release.teaser) {
|
||||
// poster will be on trailer video
|
||||
return photosWithChapterPosters;
|
||||
return photosWithClipPosters;
|
||||
}
|
||||
|
||||
if (this.release.poster) {
|
||||
// no trailer, add poster to photos
|
||||
return [this.release.poster].concat(this.release.photos).concat(photosWithChapterPosters);
|
||||
return [this.release.poster].concat(this.release.photos).concat(photosWithClipPosters);
|
||||
}
|
||||
|
||||
// no poster available
|
||||
return photosWithChapterPosters;
|
||||
return photosWithClipPosters;
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
@ -87,10 +87,17 @@
|
|||
</span>
|
||||
|
||||
<div class="labels">
|
||||
<span
|
||||
v-if="release.shootId"
|
||||
<router-link
|
||||
v-if="release.shootId && release.studio"
|
||||
:to="{ name: 'studio', params: { entitySlug: release.studio.slug } }"
|
||||
:title="release.studio && release.studio.name"
|
||||
class="shoot"
|
||||
class="shoot nolink"
|
||||
>{{ release.shootId }}</router-link>
|
||||
|
||||
<span
|
||||
v-else-if="release.shootId"
|
||||
:title="release.studio && release.studio.name"
|
||||
class="shoot nolink"
|
||||
>{{ release.shootId }}</span>
|
||||
|
||||
<ul
|
||||
|
|
|
@ -99,51 +99,6 @@
|
|||
<p class="description">{{ release.description }}</p>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
v-if="release.chapters && release.chapters.length > 0"
|
||||
class="chapters row nolist"
|
||||
>
|
||||
<span class="row-label">Chapters</span>
|
||||
<li
|
||||
v-for="chapter in release.chapters"
|
||||
:key="`chapter-${chapter.id}`"
|
||||
class="chapter"
|
||||
>
|
||||
<a
|
||||
v-if="chapter.poster"
|
||||
:href="`/media/${chapter.poster.path}`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="chapter-poster-link"
|
||||
>
|
||||
<img
|
||||
:src="`/media/${chapter.poster.thumbnail}`"
|
||||
class="chapter-poster"
|
||||
>
|
||||
</a>
|
||||
|
||||
<div class="chapter-info">
|
||||
<div class="chapter-header">
|
||||
<h3 class="chapter-title">{{ chapter.title }}</h3>
|
||||
<span
|
||||
v-if="chapter.duration"
|
||||
class="chapter-duration"
|
||||
>{{ chapter.duration }}</span>
|
||||
</div>
|
||||
|
||||
<p
|
||||
v-if="chapter.description"
|
||||
class="chapter-description"
|
||||
>{{ chapter.description }}</p>
|
||||
|
||||
<ul class="nolist"><li
|
||||
v-for="tag in chapter.tags"
|
||||
:key="`chapter-tag-${tag.id}`"
|
||||
>{{ tag.name }}</li></ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="row row-tidbits">
|
||||
<div
|
||||
v-if="release.duration"
|
||||
|
@ -151,14 +106,7 @@
|
|||
>
|
||||
<span class="row-label">Duration</span>
|
||||
|
||||
<div class="duration">
|
||||
<span
|
||||
v-if="release.duration >= 3600"
|
||||
class="duration-segment"
|
||||
>{{ Math.floor(release.duration / 3600).toString().padStart(2, '0') }}:</span>
|
||||
<span class="duration-segment">{{ Math.floor((release.duration % 3600) / 60).toString().padStart(2, '0') }}:</span>
|
||||
<span class="duration-segment">{{ (release.duration % 60).toString().padStart(2, '0') }}</span>
|
||||
</div>
|
||||
<div class="duration">{{ formatDuration(release.duration) }}</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
@ -216,6 +164,15 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="release.clips && release.clips.length > 0"
|
||||
class="row nolist"
|
||||
>
|
||||
<span class="row-label">Clips</span>
|
||||
|
||||
<Clips :clips="release.clips" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="release.comment"
|
||||
class="row"
|
||||
|
@ -241,6 +198,7 @@
|
|||
import Media from './media.vue';
|
||||
import Details from './details.vue';
|
||||
import Tags from './tags.vue';
|
||||
import Clips from './clips.vue';
|
||||
import Actor from '../actors/tile.vue';
|
||||
import Scroll from '../scroll/scroll.vue';
|
||||
import Expand from '../expand/expand.vue';
|
||||
|
@ -262,6 +220,7 @@ export default {
|
|||
Media,
|
||||
Scroll,
|
||||
Expand,
|
||||
Clips,
|
||||
Tags,
|
||||
},
|
||||
data() {
|
||||
|
@ -346,14 +305,6 @@ export default {
|
|||
margin: -.25rem 0 0 0;
|
||||
}
|
||||
|
||||
.duration {
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.duration-segment {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.actors {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
|
||||
|
@ -395,36 +346,6 @@ export default {
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chapter {
|
||||
display: flex;
|
||||
background: var(--background);
|
||||
box-shadow: 0 0 3px var(--shadow-weak);
|
||||
margin: 0 0 .5rem 0;
|
||||
}
|
||||
|
||||
.chapter-poster {
|
||||
width: 12rem;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.chapter-info {
|
||||
flex-grow: 1;
|
||||
padding: 1rem 1rem .5rem 1rem;
|
||||
}
|
||||
|
||||
.chapter-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.chapter-title {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.flag {
|
||||
height: 1rem;
|
||||
margin: 0 0 -.15rem .1rem;
|
||||
|
|
|
@ -69,7 +69,7 @@ function curateRelease(release) {
|
|||
|
||||
if (release.scenes) curatedRelease.scenes = release.scenes.map(({ scene }) => curateRelease(scene));
|
||||
if (release.movies) curatedRelease.movies = release.movies.map(({ movie }) => curateRelease(movie));
|
||||
if (release.chapters) curatedRelease.chapters = release.chapters.map(chapter => curateRelease(chapter));
|
||||
if (release.clips) curatedRelease.clips = release.clips.map(clip => curateRelease(clip));
|
||||
if (release.photos) curatedRelease.photos = release.photos.map(({ media }) => media);
|
||||
if (release.covers) curatedRelease.covers = release.covers.map(({ media }) => media);
|
||||
if (release.trailer) curatedRelease.trailer = release.trailer.media;
|
||||
|
|
|
@ -255,19 +255,19 @@ const releaseFragment = `
|
|||
${releaseTrailerFragment}
|
||||
${releaseTeaserFragment}
|
||||
${siteFragment}
|
||||
chapters {
|
||||
clips {
|
||||
id
|
||||
title
|
||||
description
|
||||
duration
|
||||
tags: chaptersTags {
|
||||
tags: clipsTags {
|
||||
tag {
|
||||
id
|
||||
name
|
||||
slug
|
||||
}
|
||||
}
|
||||
poster: chaptersPosterByChapterId {
|
||||
poster: clipsPosterByClipId {
|
||||
media {
|
||||
index
|
||||
path
|
||||
|
|
|
@ -14,6 +14,20 @@ import Container from '../components/container/container.vue';
|
|||
import Icon from '../components/icon/icon.vue';
|
||||
import Footer from '../components/footer/footer.vue';
|
||||
|
||||
function formatDuration(duration, forceHours) {
|
||||
const hours = Math.floor(duration / 3600);
|
||||
const minutes = Math.floor((duration % 3600) / 60);
|
||||
const seconds = Math.floor(duration % 60);
|
||||
|
||||
const [formattedHours, formattedMinutes, formattedSeconds] = [hours, minutes, seconds].map(segment => segment.toString().padStart(2, '0'));
|
||||
|
||||
if (duration >= 3600 || forceHours) {
|
||||
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
|
||||
}
|
||||
|
||||
return `${formattedMinutes}:${formattedSeconds}`;
|
||||
}
|
||||
|
||||
function formatDate(date, format = 'MMMM D, YYYY', precision = 'day') {
|
||||
if (precision === 'year') {
|
||||
const newFormat = format.match(/Y+/);
|
||||
|
@ -54,6 +68,7 @@ function init() {
|
|||
},
|
||||
methods: {
|
||||
formatDate,
|
||||
formatDuration,
|
||||
isAfter: (dateA, dateB) => dayjs(dateA).isAfter(dateB),
|
||||
isBefore: (dateA, dateB) => dayjs(dateA).isBefore(dateB),
|
||||
},
|
||||
|
|
|
@ -853,7 +853,7 @@ exports.up = knex => Promise.resolve()
|
|||
.references('id')
|
||||
.inTable('media');
|
||||
}))
|
||||
.then(() => knex.schema.createTable('chapters', (table) => {
|
||||
.then(() => knex.schema.createTable('clips', (table) => {
|
||||
table.increments('id', 16);
|
||||
|
||||
table.integer('release_id', 12)
|
||||
|
@ -861,9 +861,9 @@ exports.up = knex => Promise.resolve()
|
|||
.inTable('releases')
|
||||
.notNullable();
|
||||
|
||||
table.integer('chapter', 6);
|
||||
table.integer('clip', 6);
|
||||
|
||||
table.unique(['release_id', 'chapter']);
|
||||
table.unique(['release_id', 'clip']);
|
||||
|
||||
table.text('title');
|
||||
table.text('description');
|
||||
|
@ -882,44 +882,44 @@ exports.up = knex => Promise.resolve()
|
|||
table.datetime('created_at')
|
||||
.defaultTo(knex.fn.now());
|
||||
}))
|
||||
.then(() => knex.schema.createTable('chapters_posters', (table) => {
|
||||
table.integer('chapter_id', 16)
|
||||
.then(() => knex.schema.createTable('clips_posters', (table) => {
|
||||
table.integer('clip_id', 16)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('chapters');
|
||||
.inTable('clips');
|
||||
|
||||
table.text('media_id', 21)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('media');
|
||||
|
||||
table.unique('chapter_id');
|
||||
table.unique('clip_id');
|
||||
}))
|
||||
.then(() => knex.schema.createTable('chapters_photos', (table) => {
|
||||
table.integer('chapter_id', 16)
|
||||
.then(() => knex.schema.createTable('clips_photos', (table) => {
|
||||
table.integer('clip_id', 16)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('chapters');
|
||||
.inTable('clips');
|
||||
|
||||
table.text('media_id', 21)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('media');
|
||||
|
||||
table.unique(['chapter_id', 'media_id']);
|
||||
table.unique(['clip_id', 'media_id']);
|
||||
}))
|
||||
.then(() => knex.schema.createTable('chapters_tags', (table) => {
|
||||
.then(() => knex.schema.createTable('clips_tags', (table) => {
|
||||
table.integer('tag_id', 12)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('tags');
|
||||
|
||||
table.integer('chapter_id', 16)
|
||||
table.integer('clip_id', 16)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('chapters');
|
||||
.inTable('clips');
|
||||
|
||||
table.unique(['tag_id', 'chapter_id']);
|
||||
table.unique(['tag_id', 'clip_id']);
|
||||
}))
|
||||
// SEARCH
|
||||
.then(() => { // eslint-disable-line arrow-body-style
|
||||
|
@ -1100,9 +1100,9 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style
|
|||
DROP TABLE IF EXISTS movies_scenes CASCADE;
|
||||
DROP TABLE IF EXISTS movies_trailers CASCADE;
|
||||
|
||||
DROP TABLE IF EXISTS chapters_tags CASCADE;
|
||||
DROP TABLE IF EXISTS chapters_posters CASCADE;
|
||||
DROP TABLE IF EXISTS chapters_photos CASCADE;
|
||||
DROP TABLE IF EXISTS clips_tags CASCADE;
|
||||
DROP TABLE IF EXISTS clips_posters CASCADE;
|
||||
DROP TABLE IF EXISTS clips_photos CASCADE;
|
||||
|
||||
DROP TABLE IF EXISTS batches CASCADE;
|
||||
|
||||
|
@ -1122,7 +1122,7 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style
|
|||
DROP TABLE IF EXISTS tags_posters CASCADE;
|
||||
DROP TABLE IF EXISTS tags_photos CASCADE;
|
||||
DROP TABLE IF EXISTS movies CASCADE;
|
||||
DROP TABLE IF EXISTS chapters CASCADE;
|
||||
DROP TABLE IF EXISTS clips CASCADE;
|
||||
DROP TABLE IF EXISTS releases CASCADE;
|
||||
DROP TABLE IF EXISTS actors CASCADE;
|
||||
DROP TABLE IF EXISTS directors CASCADE;
|
||||
|
|
|
@ -58,7 +58,7 @@ const groups = [
|
|||
|
||||
const tags = [
|
||||
{
|
||||
name: '3d',
|
||||
name: '3D',
|
||||
slug: '3d',
|
||||
description: 'Available in 3D.',
|
||||
},
|
||||
|
|
|
@ -146,6 +146,12 @@ const studios = [
|
|||
url: 'https://www.legalporno.com/studios/kinky-sex',
|
||||
parent: 'legalporno',
|
||||
},
|
||||
{
|
||||
slug: 'sexyangelproductions',
|
||||
name: 'Sexy Angel Productions',
|
||||
url: 'https://www.legalporno.com/studios/sexy-angel-productions',
|
||||
parent: 'legalporno',
|
||||
},
|
||||
{
|
||||
slug: 'nfstudio',
|
||||
name: 'N&F Studio',
|
||||
|
|
|
@ -38,7 +38,7 @@ function scrapeScene({ query, html }, url, channel) {
|
|||
|
||||
release.poster = qu.prefixUrl(html.match(/background-image: url\('(.*)'\)/)?.[1], channel.url);
|
||||
|
||||
release.chapters = query.all('.ClipOuter').map((el) => {
|
||||
release.clips = query.all('.ClipOuter').map((el) => {
|
||||
const chapter = {};
|
||||
|
||||
chapter.title = query.text(el, 'h4');
|
||||
|
|
|
@ -49,7 +49,10 @@ async function getTrailer(scene, site, url) {
|
|||
file: scene.previewVideoUrl1080P,
|
||||
sizes: qualities.join('+'),
|
||||
type: 'trailer',
|
||||
}, { referer: url });
|
||||
}, {
|
||||
referer: url,
|
||||
origin: site.url,
|
||||
});
|
||||
|
||||
if (!tokenRes.ok) {
|
||||
return null;
|
||||
|
|
|
@ -243,44 +243,44 @@ async function updateReleasesSearch(releaseIds) {
|
|||
}
|
||||
}
|
||||
|
||||
async function storeChapters(releases) {
|
||||
const chapters = releases.map(release => release.chapters?.map((chapter, index) => ({
|
||||
title: chapter.title,
|
||||
description: chapter.description,
|
||||
async function storeClips(releases) {
|
||||
const clips = releases.map(release => release.clips?.map((clip, index) => ({
|
||||
title: clip.title,
|
||||
description: clip.description,
|
||||
releaseId: release.id,
|
||||
chapter: index + 1,
|
||||
duration: chapter.duration,
|
||||
poster: chapter.poster,
|
||||
photos: chapter.photos,
|
||||
tags: chapter.tags,
|
||||
clip: index + 1,
|
||||
duration: clip.duration,
|
||||
poster: clip.poster,
|
||||
photos: clip.photos,
|
||||
tags: clip.tags,
|
||||
}))).flat().filter(Boolean);
|
||||
|
||||
const curatedChapterEntries = chapters.map(chapter => ({
|
||||
title: chapter.title,
|
||||
description: chapter.description,
|
||||
duration: chapter.duration,
|
||||
release_id: chapter.releaseId,
|
||||
chapter: chapter.chapter,
|
||||
const curatedClipEntries = clips.map(clip => ({
|
||||
title: clip.title,
|
||||
description: clip.description,
|
||||
duration: clip.duration,
|
||||
release_id: clip.releaseId,
|
||||
clip: clip.clip,
|
||||
}));
|
||||
|
||||
const storedChapters = await bulkInsert('chapters', curatedChapterEntries);
|
||||
const chapterIdsByReleaseIdAndChapter = storedChapters.reduce((acc, chapter) => ({
|
||||
const storedClips = await bulkInsert('clips', curatedClipEntries);
|
||||
const clipIdsByReleaseIdAndClip = storedClips.reduce((acc, clip) => ({
|
||||
...acc,
|
||||
[chapter.release_id]: {
|
||||
...acc[chapter.release_id],
|
||||
[chapter.chapter]: chapter.id,
|
||||
[clip.release_id]: {
|
||||
...acc[clip.release_id],
|
||||
[clip.clip]: clip.id,
|
||||
},
|
||||
}), {});
|
||||
|
||||
const chaptersWithId = chapters.map(chapter => ({
|
||||
...chapter,
|
||||
id: chapterIdsByReleaseIdAndChapter[chapter.releaseId][chapter.chapter],
|
||||
const clipsWithId = clips.map(clip => ({
|
||||
...clip,
|
||||
id: clipIdsByReleaseIdAndClip[clip.releaseId][clip.clip],
|
||||
}));
|
||||
|
||||
await associateReleaseTags(chaptersWithId, 'chapter');
|
||||
await associateReleaseTags(clipsWithId, 'clip');
|
||||
|
||||
// media is more error-prone, associate separately
|
||||
await associateReleaseMedia(chaptersWithId, 'chapter');
|
||||
await associateReleaseMedia(clipsWithId, 'clip');
|
||||
}
|
||||
|
||||
async function storeScenes(releases) {
|
||||
|
@ -318,7 +318,7 @@ async function storeScenes(releases) {
|
|||
await scrapeActors(actors.map(actor => actor.name));
|
||||
}
|
||||
|
||||
await storeChapters(releasesWithId);
|
||||
await storeClips(releasesWithId);
|
||||
|
||||
logger.info(`Stored ${storedReleaseEntries.length} releases`);
|
||||
|
||||
|
|
Loading…
Reference in New Issue