diff --git a/.gitignore b/.gitignore index dd87e2d..ed00b3a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules build +public/fonts diff --git a/README.md b/README.md index 3670b58..a74a663 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # YouTube Maze -Some YouTube creators have made video “gamebooks” by creating videos in which viewers can make choices that influence the course of the narration. +Some YouTube creators have made video-gamebooks during which viewers can make choices that influence the course of the narration. This is generally implemented by breaking up the video into short interlinked segments. -This project aims at automatically exploring those video mazes and creating a visual representation of the underlying network. +This tool automatically generates a visual representation of networks underlying such games by automatically exploring the video maze starting from a root video. [See it live →](https://youtube-maze.cloud.delab.re) diff --git a/public/index.html b/public/index.html index f34978f..b8daf5c 100644 --- a/public/index.html +++ b/public/index.html @@ -8,7 +8,30 @@

YouTube Maze

-

e-penser

-

Defakator

+

+ Some YouTube creators have made video-gamebooks during which viewers can make choices that influence the course of the narration. + This is generally implemented by breaking up the video into short interlinked segments. + This tool automatically generates a visual representation of networks underlying such games by automatically exploring the video maze starting from a root video. +

+ +

+ See the source code → +

+ +

Generate a graph

+ +
+

+ + +

+
+ +

Some examples

+ + diff --git a/public/index2.html b/public/index2.html deleted file mode 100644 index e206e9d..0000000 --- a/public/index2.html +++ /dev/null @@ -1,465 +0,0 @@ - - - - - Exploration du labyrinthe du 1er avril d’e-penser - - - -

Exploration du labyrinthe du 1er avril d’e-penser

- -

- À l’occasion du 1er avril 2017, le vulgarisateur français e-penser a posté sur YouTube un réseau de vidéos interconnectées à la manière d’un labyrinthe. À chaque vidéo, le vidéaste nous laisse le choix d’en visionner deux autres, et nous met au défi de toutes les regarder pour parvenir à trouver une vidéo mystérieuse. -

- -

- Ce qui suit est le graphe issu de l’exploration automatisée du labyrinthe de vidéos. Chaque vidéo est cliquable. La première vidéo du graphe vous permettra de comprendre les règles établies par Bruce, la dernière est la « vidéo mystère ». -

- -

- - Plus d’infos sur l’exploration » - -

- -
- - - - epenser - - - EZGra6O8ClQ - - - 1 avril 2017 : présentation des règles - - - - - Ao990NQwn1Y - - - Vidéo sérieuse - - - - - EZGra6O8ClQ->Ao990NQwn1Y - - - - - OQloldjDS6w - - - TOP 14 DES FILMS AVEC UN TWIST DE FIN (SANS SPOIL) 🤐 - - - - - EZGra6O8ClQ->OQloldjDS6w - - - - - TabnloyIsz8 - - - Lien exemple, ne pas cliquer - - - - - EZGra6O8ClQ->TabnloyIsz8 - - - - - t2sBrJB0dCY - - - LE MENSONGE APOLLO XI 🌕 🌔 🌓 🌒 🌑 - - - - - Ao990NQwn1Y->t2sBrJB0dCY - - - - - EZjqL8kSkCE - - - Plus d'arguments sur la Terre plate - - - - - Ao990NQwn1Y->EZjqL8kSkCE - - - - - Hxho4NmIUi8 - - - TOP 6 DES SERIES MECONNUES 🤓 - - - - - OQloldjDS6w->Hxho4NmIUi8 - - - - - MgxGVSxIQbY - - - La liste de tous mes 📀 DVD 📀 - - - - - OQloldjDS6w->MgxGVSxIQbY - - - - - cUAPNwbZNzg - - - 1 avril 2017 : présentation des règles - - - - - TabnloyIsz8->cUAPNwbZNzg - - - - - t2sBrJB0dCY->OQloldjDS6w - - - - - gNO13ad_KpY - - - Une vidéo satisfaisante... 😌 - - - - - t2sBrJB0dCY->gNO13ad_KpY - - - - - EZjqL8kSkCE->OQloldjDS6w - - - - - EZjqL8kSkCE->t2sBrJB0dCY - - - - - Hxho4NmIUi8->MgxGVSxIQbY - - - - - Sw_iXCxB3Aw - - - TOP 4 DES FILMS LES PLUS SURÉVALUÉS 😤 - - - - - Hxho4NmIUi8->Sw_iXCxB3Aw - - - - - vX1CVTDopo0 - - - La liste de tous mes 📀 DVD 📀 - - - - - MgxGVSxIQbY->vX1CVTDopo0 - - - - - 54aErO8O6g0 - - - TOP 10 DES FILMS DE SCIENCE FICTION 👾 - - - - - MgxGVSxIQbY->54aErO8O6g0 - - - - - cUAPNwbZNzg->Ao990NQwn1Y - - - - - cUAPNwbZNzg->54aErO8O6g0 - - - - - U5swVaaiDI0 - - - Lien exemple, ne pas cliquer - - - - - cUAPNwbZNzg->U5swVaaiDI0 - - - - - Sw_iXCxB3Aw->54aErO8O6g0 - - - - - gGaqlwG1jOU - - - Unboxing n°2  ✂📦 - - - - - Sw_iXCxB3Aw->gGaqlwG1jOU - - - - - vX1CVTDopo0->MgxGVSxIQbY - - - - - vX1CVTDopo0->54aErO8O6g0 - - - - - 54aErO8O6g0->MgxGVSxIQbY - - - - - DIdfsbSHc5k - - - TOPCEPTION DES 5 FILMS DE MES 6 RÉALISATEURS PRÉFÉRÉS 🤒 - - - - - 54aErO8O6g0->DIdfsbSHc5k - - - - - U5swVaaiDI0->cUAPNwbZNzg - - - - - gGaqlwG1jOU->vX1CVTDopo0 - - - - - 3p8UjydB3uo - - - TOP 5 DES FILMS MÉCONNUS 😯 - - - - - gGaqlwG1jOU->3p8UjydB3uo - - - - - DIdfsbSHc5k->MgxGVSxIQbY - - - - - -kSgJdYLaUg - - - TOP 9 DES FILMS D'ARTS MARTIAUX 👊 - - - - - DIdfsbSHc5k->-kSgJdYLaUg - - - - - 3p8UjydB3uo->MgxGVSxIQbY - - - - - puHJLuBGsXU - - - TOP 5 DES COMEDIES MUSICALES 🎼 - - - - - 3p8UjydB3uo->puHJLuBGsXU - - - - - -kSgJdYLaUg->MgxGVSxIQbY - - - - - -kSgJdYLaUg->Sw_iXCxB3Aw - - - - - puHJLuBGsXU->MgxGVSxIQbY - - - - - MgemnP19JQA - - - TOP 10 DES PAGES WIKIPEDIA LES + VISITÉES 🌍 - - - - - puHJLuBGsXU->MgemnP19JQA - - - - - MgemnP19JQA->MgxGVSxIQbY - - - - - 9TEoWhqHgC8 - - - TOP 10 DES BONS GROS FILMS D'ACTIONS 👉⚔🔫🎥 - - - - - MgemnP19JQA->9TEoWhqHgC8 - - - - - 9TEoWhqHgC8->DIdfsbSHc5k - - - - - lDbkj6uMH00 - - - Unboxing n°1  ✂📦 - - - - - 9TEoWhqHgC8->lDbkj6uMH00 - - - - - lDbkj6uMH00->gGaqlwG1jOU - - - - - AbfsEn_Ai2M - - - TOP 8 DES FILMS SUR LE VOYAGE DANS LE TEMPS 🕝🕒🕜🕖 - - - - - lDbkj6uMH00->AbfsEn_Ai2M - - - - - AbfsEn_Ai2M->lDbkj6uMH00 - - - - - 3OAdSASoYu0 - - - TOP 10 DES BONS GROS FILMS D'ACTIONS 👉⚔🔫🎥 - - - - - AbfsEn_Ai2M->3OAdSASoYu0 - - - - - 3OAdSASoYu0->DIdfsbSHc5k - - - - - x6kjUMLpZaE - - - Unboxing n°1 ✂📦 - - - - - 3OAdSASoYu0->x6kjUMLpZaE - - - - - - - diff --git a/public/style.css b/public/style.css index 9453385..6cfa2d5 100644 --- a/public/style.css +++ b/public/style.css @@ -1,8 +1,477 @@ -body { - padding: 50px; - font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +/** + * Font loading + */ + +@font-face +{ + font-family: 'Equity'; + src: url('fonts/equity/equity_text_b_regular.woff2') format('woff2'), + url('fonts/equity/equity_text_b_regular.woff') format('woff'); +} + +@font-face +{ + font-family: 'Equity'; + font-style: italic; + src: url('fonts/equity/equity_text_b_italic.woff2') format('woff2'), + url('fonts/equity/equity_text_b_italic.woff') format('woff'); +} + +@font-face +{ + font-family: 'Concourse'; + src: url('fonts/concourse/concourse_c3_regular.woff2') format('woff2'), + url('fonts/concourse/concourse_c3_regular.woff') format('woff'); +} + + +/** + * Common values + */ + +:root +{ + /* text and element colors */ + --text-color: #333; + --text-accent-color: #675148; + + --background-color: #f0f0f0; + --background-lighter-color: #fafafa; + + /* grid base */ + --base-unit: 30px; + + /* fonts */ + --size-m1: .8rem; + --size-0: 21px; + --size-1: 1.666rem; + --size-2: 3rem; + + --font-display: 'Concourse'; + --font-main: 'Equity'; +} + +/** + * Common elements + */ + +body +{ + font-family: var(--font-main); + font-size: var(--size-0); + line-height: var(--base-unit); + color: var(--text-color); + + width: 15cm; + margin: calc(4 * var(--base-unit)) auto; + background: var(--background-color); + + text-align: justify; + hyphens: auto; +} + +@media screen and (max-width: 900px) +{ + body + { + width: 100%; + padding: 0 calc(4 * var(--base-unit)); + } + + br.flexible + { + display: none; + } +} + +@media screen and (max-width: 500px) +{ + body + { + padding: 0 calc(2 * var(--base-unit)); + } +} + +h1, h2 +{ + font-family: var(--font-display); + text-transform: lowercase; +} + +h3, h4, h5, h6 +{ + font-family: var(--font-main); +} + +h1, h2, h3, h4, h5, h6 +{ + text-align: left; + hyphens: none; + font-weight: normal; + color: var(--text-accent-color); +} + +h1 +{ + font-size: var(--size-2); + line-height: calc(2 * var(--base-unit)); + margin: calc(2 * var(--base-unit)) 0 var(--base-unit) 0; +} + +h2 +{ + font-size: var(--size-1); + line-height: calc(1.5 * var(--base-unit)); + margin: calc(2 * var(--base-unit)) 0 var(--base-unit) 0; +} + +h3 +{ + font-size: var(--size-0); + line-height: var(--base-unit); + margin: var(--base-unit) 0 calc(.5 * var(--base-unit)) 0; +} + +h4, h5, h6 +{ + margin: var(--base-unit) 0 0 0; +} + +a +{ + color: inherit; + text-underline-offset: 4px; +} + +a:hover +{ + color: var(--text-accent-color); +} + +p, ul, ol +{ + margin: 0 0 var(--base-unit) 0; +} + +.note +{ + font-size: var(--size-m1); + line-height: calc(var(--base-unit) * 0.7); +} + +ul, ol +{ + padding: 0; + list-style-position: outside; +} + +ul +{ + list-style: none; +} + +input, textarea +{ + display: block; + width: 100%; + + font-family: var(--font-main); + font-size: var(--size-0); + line-height: var(--base-unit); + + background: var(--background-lighter-color); + color: var(--text-color); + + border: 1px solid currentColor; + padding: + calc(.25 * var(--base-unit)) + calc(.5 * var(--base-unit)); + margin: + calc(.25 * var(--base-unit)) + 0 0 0; +} + +textarea +{ + resize: vertical; + height: 8em; +} + +input:focus, textarea:focus +{ + outline: 1px solid currentColor; +} + +input[type="submit"], button +{ + cursor: pointer; +} + +blockquote, .blockstrong +{ + margin: calc(2 * var(--base-unit)) 0 + calc(2 * var(--base-unit)) 0; + + padding: var(--base-unit); + border-radius: 2px; + background: white; + + position: relative; +} + +@media screen and (min-width: 501px) +{ + blockquote::before + { + content: "“"; + display: inline-block; + position: absolute; + + top: var(--base-unit); + left: calc(-2.5 * var(--base-unit)); + + font-size: var(--size-2); + } + + blockquote:lang(fr)::before + { + content: "«"; + } + + blockquote::after + { + content: "”"; + display: inline-block; + position: absolute; + + bottom: var(--base-unit); + right: calc(-2.5 * var(--base-unit)); + + font-size: var(--size-2); + } + + blockquote:lang(fr)::after + { + content: "»"; + } +} + +@media screen and (max-width: 500px) +{ + blockquote p:first-of-type::before + { + content: "“"; + } + + blockquote:lang(fr) p:first-of-type::before + { + content: "« "; + } + + blockquote p:last-of-type::after + { + content: "”"; + } + + blockquote:lang(fr) p:last-of-type::after + { + content: " »"; + } +} + +blockquote footer +{ + margin-top: var(--base-unit); +} + +.blockstrong :last-child +{ + margin-bottom: 0; +} + +*, *::before, *::after +{ + box-sizing: border-box; +} + +/** + * Language switcher + */ + +.language-switcher +{ + float: right; + + padding-left: calc(2 * var(--base-unit)); + margin-top: .8rem; +} + +.language-switcher .active +{ + color: var(--text-accent-color); +} + +/** + * Name and avatar + */ + +.avatar +{ + width: calc(4 * var(--base-unit)); + border-radius: 100%; + float: left; + + margin: + .3rem /* to improve alignment with neighboring title */ + 0 0 + calc(-6 * var(--base-unit)); +} + +@media screen and (max-width: 900px) +{ + .avatar + { + display: block; + float: none; + + margin: 0 0 var(--base-unit) 0; + } +} + +/** + * Columns + */ + +.columns +{ + display: flex; + justify-content: space-between; +} + +.column +{ + width: 45%; } -a { - color: #00B7FF; +@media screen and (max-width: 500px) +{ + .columns + { + display: block; + } + + .column + { + width: 100%; + } +} + +/* Workaround no margin collapsing for flex items */ +@media screen and (min-width: 501px) +{ + .column :first-child + { + margin-top: 0; + } + + .column :last-child + { + margin-bottom: 0; + } +} + +/** + * Boids + */ + + +#boids-canvas +{ + position: absolute; + top: 0; + left: 0; + z-index: -1; +} + +/** + * Books + */ + +.book +{ + display: flex; + text-align: left; + align-items: top; +} + +.book-cover +{ + line-height: 0px; + margin: 0 var(--base-unit) 0 0; + flex: 0 0 calc(4 * var(--base-unit)); +} + +.book-cover img +{ + width: 100%; +} + +.book-details dt +{ + font-family: "Concourse"; + text-transform: lowercase; +} + +.book-details dt, .book-details dd +{ + display: block; + margin: 0; + float: left; +} + +.book-details dt +{ + width: 7ch; + clear: both; +} + +.book-details dt:lang(en)::after +{ + content: ":"; +} + +.book-details dt:lang(fr)::after +{ + content: " :"; +} + +.book-details::after +{ + content: ""; + display: block; + clear: both; +} + +.book-description h3 +{ + margin: 0; +} + +.book-description address +{ + margin-bottom: var(--base-unit); +} + +@media screen and (max-width: 500px) +{ + .book + { + flex-direction: column; + align-items: stretch; + } + + .book-cover + { + margin: 0 0 var(--base-unit) 0; + } } diff --git a/public/style2.css b/public/style2.css deleted file mode 100644 index 7820769..0000000 --- a/public/style2.css +++ /dev/null @@ -1,18 +0,0 @@ -html -{ - margin: 0; - padding: 0; -} - -body -{ - padding: 4em 6em; - font-family: sans-serif; -} - -hr -{ - border: 0; - border-bottom: 1px solid black; - margin-bottom: 2em; -} diff --git a/routes/mazes.mjs b/routes/mazes.mjs index 48ef547..b4d5185 100644 --- a/routes/mazes.mjs +++ b/routes/mazes.mjs @@ -9,6 +9,37 @@ const statusPending = Symbol('PENDING'); const statusError = Symbol('ERROR'); const cache = Object.create(null); +// Extract the video ID from a YouTube URL +const YOUTUBE_URL_REGEX = /(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?v=([^?&]+)/; +const YOUTUBE_SHORT_URL_REGEX = /(?:https?:\/\/)?(?:www\.)?youtu\.be\/([^?&]+)/; + +router.post('/', (req, res) => { + if (req.body.url === '') + { + res.redirect('/'); + return; + } + + const url = req.body.url.match(YOUTUBE_URL_REGEX); + + if (url !== null) + { + res.redirect(`/mazes/${url[1]}`); + return; + } + + const shortURL = req.body.url.match(YOUTUBE_SHORT_URL_REGEX); + + if (shortURL !== null) + { + res.redirect(`/mazes/${shortURL[1]}`); + return; + } + + res.redirect(`/mazes/${req.body.url}`); +}); + +// Generate the graph for a given video ID router.get('/:videoId', async (req, res) => { const {videoId} = req.params;