diff --git a/assets/components/releases/clips.vue b/assets/components/releases/clips.vue new file mode 100644 index 00000000..3494a577 --- /dev/null +++ b/assets/components/releases/clips.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/assets/components/releases/media.vue b/assets/components/releases/media.vue index 249d8eb7..c491a9bb 100644 --- a/assets/components/releases/media.vue +++ b/assets/components/releases/media.vue @@ -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 { diff --git a/assets/components/releases/scene-tile.vue b/assets/components/releases/scene-tile.vue index 5fd53b39..4540f91b 100644 --- a/assets/components/releases/scene-tile.vue +++ b/assets/components/releases/scene-tile.vue @@ -87,10 +87,17 @@
- {{ release.shootId }} + + {{ release.shootId }}
- -
Duration -
- {{ Math.floor(release.duration / 3600).toString().padStart(2, '0') }}: - {{ Math.floor((release.duration % 3600) / 60).toString().padStart(2, '0') }}: - {{ (release.duration % 60).toString().padStart(2, '0') }} -
+
{{ formatDuration(release.duration) }}
+
+ Clips + + +
+
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; diff --git a/assets/js/fragments.js b/assets/js/fragments.js index 45f015e4..0f0374cb 100644 --- a/assets/js/fragments.js +++ b/assets/js/fragments.js @@ -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 diff --git a/assets/js/main.js b/assets/js/main.js index faeae36c..ce3ab4a5 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -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), }, diff --git a/migrations/20190325001339_releases.js b/migrations/20190325001339_releases.js index 2f3d2cb1..bbad1083 100644 --- a/migrations/20190325001339_releases.js +++ b/migrations/20190325001339_releases.js @@ -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; diff --git a/seeds/00_tags.js b/seeds/00_tags.js index cd532887..cfc25f98 100644 --- a/seeds/00_tags.js +++ b/seeds/00_tags.js @@ -58,7 +58,7 @@ const groups = [ const tags = [ { - name: '3d', + name: '3D', slug: '3d', description: 'Available in 3D.', }, diff --git a/seeds/03_studios.js b/seeds/03_studios.js index 3f210473..19de0282 100644 --- a/seeds/03_studios.js +++ b/seeds/03_studios.js @@ -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', diff --git a/src/scrapers/inthecrack.js b/src/scrapers/inthecrack.js index 3dad77a3..29a98066 100644 --- a/src/scrapers/inthecrack.js +++ b/src/scrapers/inthecrack.js @@ -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'); diff --git a/src/scrapers/vixen.js b/src/scrapers/vixen.js index cc940f11..9ec8f280 100644 --- a/src/scrapers/vixen.js +++ b/src/scrapers/vixen.js @@ -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; diff --git a/src/store-releases.js b/src/store-releases.js index 1cfd0998..6562422a 100644 --- a/src/store-releases.js +++ b/src/store-releases.js @@ -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`);