Renamed chapters to clips. Fixed Vixen trailers.

This commit is contained in:
DebaucheryLibrarian 2020-08-20 19:52:02 +02:00
parent 2835c66694
commit 501e764c21
13 changed files with 191 additions and 150 deletions

View File

@ -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>

View File

@ -105,20 +105,20 @@ function sfw() {
} }
function photos() { 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) { if (this.release.trailer || this.release.teaser) {
// poster will be on trailer video // poster will be on trailer video
return photosWithChapterPosters; return photosWithClipPosters;
} }
if (this.release.poster) { if (this.release.poster) {
// no trailer, add poster to photos // 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 // no poster available
return photosWithChapterPosters; return photosWithClipPosters;
} }
export default { export default {

View File

@ -87,10 +87,17 @@
</span> </span>
<div class="labels"> <div class="labels">
<span <router-link
v-if="release.shootId" v-if="release.shootId && release.studio"
:to="{ name: 'studio', params: { entitySlug: release.studio.slug } }"
:title="release.studio && release.studio.name" :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> >{{ release.shootId }}</span>
<ul <ul

View File

@ -99,51 +99,6 @@
<p class="description">{{ release.description }}</p> <p class="description">{{ release.description }}</p>
</div> </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 class="row row-tidbits">
<div <div
v-if="release.duration" v-if="release.duration"
@ -151,14 +106,7 @@
> >
<span class="row-label">Duration</span> <span class="row-label">Duration</span>
<div class="duration"> <div class="duration">{{ formatDuration(release.duration) }}</div>
<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> </div>
<div <div
@ -216,6 +164,15 @@
</div> </div>
</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 <div
v-if="release.comment" v-if="release.comment"
class="row" class="row"
@ -241,6 +198,7 @@
import Media from './media.vue'; import Media from './media.vue';
import Details from './details.vue'; import Details from './details.vue';
import Tags from './tags.vue'; import Tags from './tags.vue';
import Clips from './clips.vue';
import Actor from '../actors/tile.vue'; import Actor from '../actors/tile.vue';
import Scroll from '../scroll/scroll.vue'; import Scroll from '../scroll/scroll.vue';
import Expand from '../expand/expand.vue'; import Expand from '../expand/expand.vue';
@ -262,6 +220,7 @@ export default {
Media, Media,
Scroll, Scroll,
Expand, Expand,
Clips,
Tags, Tags,
}, },
data() { data() {
@ -346,14 +305,6 @@ export default {
margin: -.25rem 0 0 0; margin: -.25rem 0 0 0;
} }
.duration {
font-size: 0;
}
.duration-segment {
font-size: 1rem;
}
.actors { .actors {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
@ -395,36 +346,6 @@ export default {
text-overflow: ellipsis; 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 { .flag {
height: 1rem; height: 1rem;
margin: 0 0 -.15rem .1rem; margin: 0 0 -.15rem .1rem;

View File

@ -69,7 +69,7 @@ function curateRelease(release) {
if (release.scenes) curatedRelease.scenes = release.scenes.map(({ scene }) => curateRelease(scene)); if (release.scenes) curatedRelease.scenes = release.scenes.map(({ scene }) => curateRelease(scene));
if (release.movies) curatedRelease.movies = release.movies.map(({ movie }) => curateRelease(movie)); 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.photos) curatedRelease.photos = release.photos.map(({ media }) => media);
if (release.covers) curatedRelease.covers = release.covers.map(({ media }) => media); if (release.covers) curatedRelease.covers = release.covers.map(({ media }) => media);
if (release.trailer) curatedRelease.trailer = release.trailer.media; if (release.trailer) curatedRelease.trailer = release.trailer.media;

View File

@ -255,19 +255,19 @@ const releaseFragment = `
${releaseTrailerFragment} ${releaseTrailerFragment}
${releaseTeaserFragment} ${releaseTeaserFragment}
${siteFragment} ${siteFragment}
chapters { clips {
id id
title title
description description
duration duration
tags: chaptersTags { tags: clipsTags {
tag { tag {
id id
name name
slug slug
} }
} }
poster: chaptersPosterByChapterId { poster: clipsPosterByClipId {
media { media {
index index
path path

View File

@ -14,6 +14,20 @@ import Container from '../components/container/container.vue';
import Icon from '../components/icon/icon.vue'; import Icon from '../components/icon/icon.vue';
import Footer from '../components/footer/footer.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') { function formatDate(date, format = 'MMMM D, YYYY', precision = 'day') {
if (precision === 'year') { if (precision === 'year') {
const newFormat = format.match(/Y+/); const newFormat = format.match(/Y+/);
@ -54,6 +68,7 @@ function init() {
}, },
methods: { methods: {
formatDate, formatDate,
formatDuration,
isAfter: (dateA, dateB) => dayjs(dateA).isAfter(dateB), isAfter: (dateA, dateB) => dayjs(dateA).isAfter(dateB),
isBefore: (dateA, dateB) => dayjs(dateA).isBefore(dateB), isBefore: (dateA, dateB) => dayjs(dateA).isBefore(dateB),
}, },

View File

@ -853,7 +853,7 @@ exports.up = knex => Promise.resolve()
.references('id') .references('id')
.inTable('media'); .inTable('media');
})) }))
.then(() => knex.schema.createTable('chapters', (table) => { .then(() => knex.schema.createTable('clips', (table) => {
table.increments('id', 16); table.increments('id', 16);
table.integer('release_id', 12) table.integer('release_id', 12)
@ -861,9 +861,9 @@ exports.up = knex => Promise.resolve()
.inTable('releases') .inTable('releases')
.notNullable(); .notNullable();
table.integer('chapter', 6); table.integer('clip', 6);
table.unique(['release_id', 'chapter']); table.unique(['release_id', 'clip']);
table.text('title'); table.text('title');
table.text('description'); table.text('description');
@ -882,44 +882,44 @@ exports.up = knex => Promise.resolve()
table.datetime('created_at') table.datetime('created_at')
.defaultTo(knex.fn.now()); .defaultTo(knex.fn.now());
})) }))
.then(() => knex.schema.createTable('chapters_posters', (table) => { .then(() => knex.schema.createTable('clips_posters', (table) => {
table.integer('chapter_id', 16) table.integer('clip_id', 16)
.notNullable() .notNullable()
.references('id') .references('id')
.inTable('chapters'); .inTable('clips');
table.text('media_id', 21) table.text('media_id', 21)
.notNullable() .notNullable()
.references('id') .references('id')
.inTable('media'); .inTable('media');
table.unique('chapter_id'); table.unique('clip_id');
})) }))
.then(() => knex.schema.createTable('chapters_photos', (table) => { .then(() => knex.schema.createTable('clips_photos', (table) => {
table.integer('chapter_id', 16) table.integer('clip_id', 16)
.notNullable() .notNullable()
.references('id') .references('id')
.inTable('chapters'); .inTable('clips');
table.text('media_id', 21) table.text('media_id', 21)
.notNullable() .notNullable()
.references('id') .references('id')
.inTable('media'); .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) table.integer('tag_id', 12)
.notNullable() .notNullable()
.references('id') .references('id')
.inTable('tags'); .inTable('tags');
table.integer('chapter_id', 16) table.integer('clip_id', 16)
.notNullable() .notNullable()
.references('id') .references('id')
.inTable('chapters'); .inTable('clips');
table.unique(['tag_id', 'chapter_id']); table.unique(['tag_id', 'clip_id']);
})) }))
// SEARCH // SEARCH
.then(() => { // eslint-disable-line arrow-body-style .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_scenes CASCADE;
DROP TABLE IF EXISTS movies_trailers CASCADE; DROP TABLE IF EXISTS movies_trailers CASCADE;
DROP TABLE IF EXISTS chapters_tags CASCADE; DROP TABLE IF EXISTS clips_tags CASCADE;
DROP TABLE IF EXISTS chapters_posters CASCADE; DROP TABLE IF EXISTS clips_posters CASCADE;
DROP TABLE IF EXISTS chapters_photos CASCADE; DROP TABLE IF EXISTS clips_photos CASCADE;
DROP TABLE IF EXISTS batches 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_posters CASCADE;
DROP TABLE IF EXISTS tags_photos CASCADE; DROP TABLE IF EXISTS tags_photos CASCADE;
DROP TABLE IF EXISTS movies 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 releases CASCADE;
DROP TABLE IF EXISTS actors CASCADE; DROP TABLE IF EXISTS actors CASCADE;
DROP TABLE IF EXISTS directors CASCADE; DROP TABLE IF EXISTS directors CASCADE;

View File

@ -58,7 +58,7 @@ const groups = [
const tags = [ const tags = [
{ {
name: '3d', name: '3D',
slug: '3d', slug: '3d',
description: 'Available in 3D.', description: 'Available in 3D.',
}, },

View File

@ -146,6 +146,12 @@ const studios = [
url: 'https://www.legalporno.com/studios/kinky-sex', url: 'https://www.legalporno.com/studios/kinky-sex',
parent: 'legalporno', parent: 'legalporno',
}, },
{
slug: 'sexyangelproductions',
name: 'Sexy Angel Productions',
url: 'https://www.legalporno.com/studios/sexy-angel-productions',
parent: 'legalporno',
},
{ {
slug: 'nfstudio', slug: 'nfstudio',
name: 'N&F Studio', name: 'N&F Studio',

View File

@ -38,7 +38,7 @@ function scrapeScene({ query, html }, url, channel) {
release.poster = qu.prefixUrl(html.match(/background-image: url\('(.*)'\)/)?.[1], channel.url); 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 = {}; const chapter = {};
chapter.title = query.text(el, 'h4'); chapter.title = query.text(el, 'h4');

View File

@ -49,7 +49,10 @@ async function getTrailer(scene, site, url) {
file: scene.previewVideoUrl1080P, file: scene.previewVideoUrl1080P,
sizes: qualities.join('+'), sizes: qualities.join('+'),
type: 'trailer', type: 'trailer',
}, { referer: url }); }, {
referer: url,
origin: site.url,
});
if (!tokenRes.ok) { if (!tokenRes.ok) {
return null; return null;

View File

@ -243,44 +243,44 @@ async function updateReleasesSearch(releaseIds) {
} }
} }
async function storeChapters(releases) { async function storeClips(releases) {
const chapters = releases.map(release => release.chapters?.map((chapter, index) => ({ const clips = releases.map(release => release.clips?.map((clip, index) => ({
title: chapter.title, title: clip.title,
description: chapter.description, description: clip.description,
releaseId: release.id, releaseId: release.id,
chapter: index + 1, clip: index + 1,
duration: chapter.duration, duration: clip.duration,
poster: chapter.poster, poster: clip.poster,
photos: chapter.photos, photos: clip.photos,
tags: chapter.tags, tags: clip.tags,
}))).flat().filter(Boolean); }))).flat().filter(Boolean);
const curatedChapterEntries = chapters.map(chapter => ({ const curatedClipEntries = clips.map(clip => ({
title: chapter.title, title: clip.title,
description: chapter.description, description: clip.description,
duration: chapter.duration, duration: clip.duration,
release_id: chapter.releaseId, release_id: clip.releaseId,
chapter: chapter.chapter, clip: clip.clip,
})); }));
const storedChapters = await bulkInsert('chapters', curatedChapterEntries); const storedClips = await bulkInsert('clips', curatedClipEntries);
const chapterIdsByReleaseIdAndChapter = storedChapters.reduce((acc, chapter) => ({ const clipIdsByReleaseIdAndClip = storedClips.reduce((acc, clip) => ({
...acc, ...acc,
[chapter.release_id]: { [clip.release_id]: {
...acc[chapter.release_id], ...acc[clip.release_id],
[chapter.chapter]: chapter.id, [clip.clip]: clip.id,
}, },
}), {}); }), {});
const chaptersWithId = chapters.map(chapter => ({ const clipsWithId = clips.map(clip => ({
...chapter, ...clip,
id: chapterIdsByReleaseIdAndChapter[chapter.releaseId][chapter.chapter], id: clipIdsByReleaseIdAndClip[clip.releaseId][clip.clip],
})); }));
await associateReleaseTags(chaptersWithId, 'chapter'); await associateReleaseTags(clipsWithId, 'clip');
// media is more error-prone, associate separately // media is more error-prone, associate separately
await associateReleaseMedia(chaptersWithId, 'chapter'); await associateReleaseMedia(clipsWithId, 'clip');
} }
async function storeScenes(releases) { async function storeScenes(releases) {
@ -318,7 +318,7 @@ async function storeScenes(releases) {
await scrapeActors(actors.map(actor => actor.name)); await scrapeActors(actors.map(actor => actor.name));
} }
await storeChapters(releasesWithId); await storeClips(releasesWithId);
logger.info(`Stored ${storedReleaseEntries.length} releases`); logger.info(`Stored ${storedReleaseEntries.length} releases`);