diff --git a/assets/components/home/home.vue b/assets/components/home/home.vue index 51e13e0a..1e082205 100644 --- a/assets/components/home/home.vue +++ b/assets/components/home/home.vue @@ -33,8 +33,15 @@ class="scene-link" > + + diff --git a/assets/components/release/release.vue b/assets/components/release/release.vue index fa7f5d4a..a8339dc0 100644 --- a/assets/components/release/release.vue +++ b/assets/components/release/release.vue @@ -4,17 +4,27 @@ class="banner" @wheel.prevent="scrollBanner" > + + @@ -90,14 +100,14 @@ function scrollBanner(event) { } function photos() { - if (this.release && this.release.photos.length) { - if (this.release.photos[0].role === 'poster') { - return this.release.photos.slice(1); - } - + if (this.release.photos.length) { return this.release.photos; } + if (this.release.poster && !this.release.trailer) { + return [this.release.poster]; + } + return []; } @@ -131,12 +141,17 @@ export default { margin: 0 0 1rem 0; scrollbar-width: none; box-shadow: 0 0 3px $shadow; + font-size: 0; &::-webkit-scrollbar { display: none; } } +.banner-trailer { + display: inline-block; +} + .banner-item { height: 20rem; vertical-align: middle; diff --git a/migrations/20190325001339_releases.js b/migrations/20190325001339_releases.js index e53fedc1..eb174636 100644 --- a/migrations/20190325001339_releases.js +++ b/migrations/20190325001339_releases.js @@ -115,14 +115,15 @@ exports.up = knex => Promise.resolve() .then(() => knex.schema.createTable('media', (table) => { table.increments('id', 16); - table.string('file'); + table.string('path'); table.integer('index'); table.string('mime'); table.enum('domain', ['networks', 'sites', 'releases', 'actors', 'directors']); table.integer('target_id', 16); - table.enum('role', ['poster', 'logo', 'profile']); + table.enum('role', ['photo', 'poster', 'trailer', 'logo', 'profile']); + table.string('quality', 6); })) .then(() => knex.schema.createTable('actors_associated', (table) => { table.increments('id', 16); diff --git a/package-lock.json b/package-lock.json index cf345c75..ab73d8df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4675,6 +4675,11 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" } } }, @@ -6969,9 +6974,9 @@ } }, "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" }, "mime-db": { "version": "1.38.0", diff --git a/package.json b/package.json index 3440387c..c77b9b81 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "fs-extra": "^7.0.1", "knex": "^0.16.3", "knex-migrate": "^1.7.1", + "mime": "^2.4.4", "moment": "^2.24.0", "neo-blessed": "^0.2.0", "opn": "^5.4.0", diff --git a/public/css/style.css b/public/css/style.css index 7e048d9c..52d2b3fc 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -114,10 +114,14 @@ margin: 0 0 1rem 0; scrollbar-width: none; box-shadow: 0 0 3px rgba(0, 0, 0, 0.5); + font-size: 0; } .banner[data-v-2bc41e74]::-webkit-scrollbar { display: none; } +.banner-trailer[data-v-2bc41e74] { + display: inline-block; +} .banner-item[data-v-2bc41e74] { height: 20rem; vertical-align: middle; diff --git a/src/fetch-releases.js b/src/fetch-releases.js index 0c51df72..07ca82ce 100644 --- a/src/fetch-releases.js +++ b/src/fetch-releases.js @@ -5,6 +5,7 @@ const fs = require('fs-extra'); const path = require('path'); const Promise = require('bluebird'); const moment = require('moment'); +const mime = require('mime'); const bhttp = require('bhttp'); const argv = require('./argv'); @@ -86,42 +87,71 @@ async function findDuplicateReleases(latestReleases, _siteId) { } async function storePhotos(release, releaseEntry) { - await fs.mkdir(path.join(config.photoPath, release.site.slug, releaseEntry.id.toString()), { recursive: true }); + console.log(`Storing ${release.photos.length} photos for (${release.site.name}, ${releaseEntry.id}) "${release.title}"`); - console.log(`Storing photos for (${release.site.name}, ${releaseEntry.id}) "${release.title}"`); + const files = await Promise.map(release.photos, async (photoUrl, index) => { + const { pathname } = new URL(photoUrl); + const mimetype = mime.getType(pathname); - const filepaths = await Promise.map(release.photos, async (photoUrl, index) => { const res = await bhttp.get(photoUrl); - const filepath = path.join(release.site.slug, releaseEntry.id.toString(), `${index + 1}.jpg`); + const filepath = path.join(release.site.slug, releaseEntry.id.toString(), `${index + 1}.${mime.getExtension(mimetype)}`); await fs.writeFile(path.join(config.photoPath, filepath), res.body); - return filepath; + return { + filepath, + mimetype, + }; }, { concurrency: 2, }); - await knex('media').insert(filepaths.map((filepath, index) => ({ - file: filepath, - mime: 'image/jpeg', + await knex('media').insert(files.map(({ filepath, mimetype }, index) => ({ + path: filepath, + mime: mimetype, index, domain: 'releases', target_id: releaseEntry.id, - role: null, + role: 'photo', }))); +} - if (release.trailer && release.trailer.poster) { - const res = await bhttp.get(release.trailer.poster); - const filepath = path.join(release.site.slug, releaseEntry.id.toString(), 'poster.jpg'); - await fs.writeFile(path.join(config.photoPath, filepath), res.body); +async function storePoster(release, releaseEntry) { + console.log(`Storing poster for (${release.site.name}, ${releaseEntry.id}) "${release.title}"`); - await knex('media').insert({ - file: filepath, - mime: 'image/jpeg', - domain: 'releases', - target_id: releaseEntry.id, - role: 'poster', - }); - } + const { pathname } = new URL(release.poster); + const mimetype = mime.getType(pathname); + + const res = await bhttp.get(release.poster); + const filepath = path.join(release.site.slug, releaseEntry.id.toString(), `poster.${mime.getExtension(mimetype)}`); + await fs.writeFile(path.join(config.photoPath, filepath), res.body); + + await knex('media').insert({ + path: filepath, + mime: mimetype, + domain: 'releases', + target_id: releaseEntry.id, + role: 'poster', + }); +} + +async function storeTrailer(release, releaseEntry) { + console.log(`Storing trailer for (${release.site.name}, ${releaseEntry.id}) "${release.title}"`); + + const { pathname } = new URL(release.trailer.src); + const mimetype = release.trailer.type || mime.getType(pathname); + + const res = await bhttp.get(release.trailer.src); + const filepath = path.join(release.site.slug, releaseEntry.id.toString(), `trailer${release.trailer.quality ? `_${release.trailer.quality}` : ''}.${mime.getExtension(mimetype)}`); + await fs.writeFile(path.join(config.photoPath, filepath), res.body); + + await knex('media').insert({ + path: filepath, + mime: mimetype, + domain: 'releases', + target_id: releaseEntry.id, + role: 'trailer', + quality: release.trailer.quality || null, + }); } async function storeReleases(releases = []) { @@ -170,9 +200,21 @@ async function storeReleases(releases = []) { }))); } + if (release.poster || (release.photos && release.photos.length)) { + await fs.mkdir(path.join(config.photoPath, release.site.slug, releaseEntry.rows[0].id.toString()), { recursive: true }); + } + if (release.photos && release.photos.length > 0) { await storePhotos(release, releaseEntry.rows[0]); } + + if (release.poster) { + await storePoster(release, releaseEntry.rows[0]); + } + + if (release.trailer) { + await storeTrailer(release, releaseEntry.rows[0]); + } }, { concurrency: 2, }); diff --git a/src/releases.js b/src/releases.js index c82fec1b..21f8d6d0 100644 --- a/src/releases.js +++ b/src/releases.js @@ -28,7 +28,9 @@ async function curateRelease(release) { actors, director: release.director, tags, - photos: media, + photos: media.filter(item => item.role === 'photo'), + poster: media.filter(item => item.role === 'poster')[0], + trailer: media.filter(item => item.role === 'trailer')[0], rating: { likes: release.likes, dislikes: release.dislikes, diff --git a/src/scrapers/kink.js b/src/scrapers/kink.js index 6ee5ab4a..94eadcdb 100644 --- a/src/scrapers/kink.js +++ b/src/scrapers/kink.js @@ -18,6 +18,7 @@ function scrapeLatest(html, site) { const shootId = href.split('/')[2]; const title = sceneLinkElement.text().trim(); + const poster = $(element).find('.adimage').attr('src'); const photos = $(element).find('.rollover .roll-image').map((photoIndex, photoElement) => $(photoElement).attr('data-imagesrc')).toArray(); const date = moment.utc($(element).find('.date').text(), 'MMM DD, YYYY').toDate(); @@ -36,6 +37,7 @@ function scrapeLatest(html, site) { actors, date, photos, + poster, rating: { stars, }, @@ -86,13 +88,10 @@ async function scrapeScene(html, url, shootId, ratingRes, site) { actors, description, photos, + poster: trailerPoster, trailer: { - video: { - default: trailerVideo, - sd: trailerVideo, - hd: trailerVideo.replace('480p', '720p'), - }, - poster: trailerPoster, + src: trailerVideo, + quality: 480, }, rating: { stars, diff --git a/src/scrapers/vixen.js b/src/scrapers/vixen.js index 6b24a014..870dfa4e 100644 --- a/src/scrapers/vixen.js +++ b/src/scrapers/vixen.js @@ -15,18 +15,32 @@ function scrapeLatest(html, site) { return scenes.map((scene) => { const shootId = String(scene.newId); - const { title } = scene; + + const { + title, + models: actors, + } = scene; + const url = `${site.url}${scene.targetUrl}`; const date = moment.utc(scene.releaseDateFormatted, 'MMMM DD, YYYY').toDate(); - const actors = scene.models; const stars = Number(scene.textRating) / 2; + // largest thumbnail. poster is the same image but bigger, too large for storage space efficiency + const poster = scene.images.listing.slice(-1)[0].src; + const trailer = scene.previews.listing.slice(-1)[0]; + return { url, shootId, title, actors, date, + poster, + trailer: { + src: trailer.src, + type: trailer.type, + quality: trailer.height, + }, rating: { stars, }, @@ -45,21 +59,21 @@ async function scrapeScene(html, url, site) { const shootId = data.page.data[`${pathname}${search}`].data.video; const scene = data.videos.find(video => video.newId === shootId); + // console.log(scene); + const { title, description, models: actors, totalRateVal: stars, + runLength: duration, + directorNames: director, + tags: rawTags, } = scene; const date = new Date(scene.releaseDate); - - const rawTags = scene.tags; const tags = await matchTags(rawTags); - const duration = scene.runLength; - const director = scene.directorNames; - return { url, shootId,