mirror of
https://github.com/mit-regressions/viewer.git
synced 2025-04-09 14:20:15 -04:00
working prototype of metadata viewer with defined schema
This commit is contained in:
parent
4709d32be0
commit
46fd752f40
|
@ -2,8 +2,80 @@ WEBVTT
|
|||
Kind: captions
|
||||
Language: en
|
||||
|
||||
NOTE We could have a separation between MUSIC SOURCE, VIDEO SOURCE, NARRATION SOURCE, COMMENTARY, and TRANSCRIPT.
|
||||
NOTE separation between MUSIC SOURCE, VIDEO SOURCE, NARRATION SOURCE, COMMENTARY, and TRANSCRIPT.
|
||||
|
||||
NOTE ["The Vanishing American Family" by ScubaZ]
|
||||
00:00:01.550 --> 00:01:15.448
|
||||
{"keywords_alt": "", "gpspoints": {"gps_zoom": "11", "gps_text_alt": "", "gps_text": "Stockholm, Sweden", "gps": "59.3388502,18.0616981"}, "synopsis": "", "subjects": "", "hyperlinks": {"hyperlink_text_alt": "", "hyperlink_text": "", "hyperlink": ""}, "synopsis_alt": "", "title": "The Vanishing American Family", "keywords": "ScubaZ", "title_alt": "", "subjects_alt": ""}
|
||||
{
|
||||
"uid": "1",
|
||||
"type": "music",
|
||||
"data": {
|
||||
"type": "music",
|
||||
"title": "The Vanishing American Family",
|
||||
"artist": "ScubaZ",
|
||||
"year": "2001",
|
||||
"label": "Odd Records Ltd.",
|
||||
"hyperlink": "https://www.discogs.com/master/1184747-Scuba-Z-The-Vanishing-American-Family"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
00:00:17.550 --> 00:00:19.548
|
||||
{
|
||||
"uid": "2",
|
||||
"type": "video_source",
|
||||
"data": {
|
||||
"type": "video_source",
|
||||
"title": "\"We lived here, you didn't...\" @ Bexley Hall, MIT",
|
||||
"artist": "Andrei Ivanov",
|
||||
"year": "2013",
|
||||
"notes": "AI-upscaled and frame-interpolated",
|
||||
"retrieved_from": "YouTube",
|
||||
"hyperlink": "https://www.youtube.com/watch?v=e6m8se96yyM&ab_channel=AndreiIvanov"
|
||||
}
|
||||
}
|
||||
|
||||
00:00:47.000 --> 00:00:48.600
|
||||
{
|
||||
"uid": "2",
|
||||
"type": "video_source",
|
||||
"data": {
|
||||
"type": "video_source",
|
||||
"title": "The November Actions",
|
||||
"artist": "Ricky Leacock",
|
||||
"year": "1969",
|
||||
"notes": "AI-upscaled and frame-interpolated",
|
||||
"retrieved_from": "MIT Museum",
|
||||
"hyperlink": ""
|
||||
}
|
||||
}
|
||||
|
||||
00:00:56.000 --> 00:01:01.600
|
||||
{
|
||||
"uid": "3",
|
||||
"type": "video_source",
|
||||
"data": {
|
||||
"type": "video_source",
|
||||
"title": "MIT: Progressions",
|
||||
"artist": "David and Sheri Espar",
|
||||
"year": "1969",
|
||||
"notes": "AI-upscaled and frame-interpolated",
|
||||
"retrieved_from": "Kenneth Friedman (YouTube)",
|
||||
"hyperlink": "https://www.youtube.com/watch?v=p3mq5E0GwLA&ab_channel=KennethFriedman"
|
||||
}
|
||||
}
|
||||
|
||||
00:01:03.800 --> 00:01:15.600
|
||||
{
|
||||
"uid": "3",
|
||||
"type": "video_source",
|
||||
"data": {
|
||||
"type": "video_source",
|
||||
"title": "Technology",
|
||||
"artist": "unknown creators",
|
||||
"year": "1934",
|
||||
"notes": "AI-upscaled and frame-interpolated. View Kathleen E. 23's <a href=\"https://mitadmissions.org/blogs/entry/5-historical-mit-videos/#video5\"><strong>post</strong></a>",
|
||||
"retrieved_from": "MIT Museum",
|
||||
"hyperlink": "https://www.youtube.com/watch?v=rsO_67xzymQ&ab_channel=FromtheVaultofMIT"
|
||||
}
|
||||
}
|
|
@ -14,9 +14,6 @@ export default function Player() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div id="control-panel" className="flex flex-row">
|
||||
<button id="show-metadata" className="bg-gray-200 hover:bg-gray-400 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-800 dark:text-gray-200 py-2 px-4">Show Metadata</button>
|
||||
</div>
|
||||
<WebVttPlayer
|
||||
preload={false}
|
||||
audio={audioUrl}
|
||||
|
|
|
@ -2,85 +2,138 @@ import React, { Component } from 'react'
|
|||
// import './MetadataPoint.css'
|
||||
|
||||
|
||||
|
||||
type MetadataPointProps = {
|
||||
cue: VTTCue
|
||||
active: boolean
|
||||
seek: (time: number) => void
|
||||
}
|
||||
|
||||
class MetadataPoint extends Component<MetadataPointProps> {
|
||||
interface MetadataPointData {
|
||||
"uid": string,
|
||||
"type": string,
|
||||
// data is one of four interfaces: music, commentary, transcript, or video_source
|
||||
"data": MusicData | CommentaryData | TranscriptData | VideoSourceData | NarrationSourceData | OhmsData
|
||||
}
|
||||
|
||||
interface MusicData {
|
||||
"type": "music",
|
||||
"title": string,
|
||||
"title_alt": string,
|
||||
"artist": string,
|
||||
"year": string,
|
||||
"label": string,
|
||||
"hyperlink": string,
|
||||
}
|
||||
|
||||
interface CommentaryData {
|
||||
"type": "commentary",
|
||||
"text": string, // may contain VTT-compatible styling, as specified at https://www.w3.org/TR/webvtt1/#model-overview
|
||||
}
|
||||
|
||||
interface TranscriptData {
|
||||
"type": "transcript",
|
||||
"text": string, // may contain VTT-compatible styling, as specified at https://www.w3.org/TR/webvtt1/#model-overview
|
||||
}
|
||||
|
||||
interface VideoSourceData {
|
||||
"type": "video_source",
|
||||
"title": string,
|
||||
"artist": string, // optional - may be empty string
|
||||
"attribution": string,
|
||||
"year": string,
|
||||
"notes": string,
|
||||
"retrieved_from": string,
|
||||
"hyperlink": string,
|
||||
}
|
||||
|
||||
interface NarrationSourceData {
|
||||
"type": "narration_source",
|
||||
"title": string,
|
||||
"attribution": string,
|
||||
"year": string,
|
||||
"retrieval_date": string,
|
||||
"source_type" : string, // PDF, etc
|
||||
|
||||
"hyperlink": string,
|
||||
}
|
||||
|
||||
// adopting the OHMS standard http://ohda.matrix.msu.edu/2014/11/indexing-interviews-in-ohms/
|
||||
interface OhmsData {
|
||||
"type": "ohms",
|
||||
"title": string,
|
||||
"title_alt": string,
|
||||
"synopsis": string,
|
||||
"synopsis_alt": string,
|
||||
"keywords": string,
|
||||
"keywords_alt": string,
|
||||
"subjects": string,
|
||||
"subjects_alt": string,
|
||||
"gpspoints": {
|
||||
"gps": string,
|
||||
"gps_zoom": string,
|
||||
"gps_text": string,
|
||||
"gps_text_alt": string
|
||||
},
|
||||
"hyperlinks": {
|
||||
"hyperlink": string,
|
||||
"hyperlink_text": string,
|
||||
"hyperlink_text_alt": string
|
||||
}
|
||||
}
|
||||
class MetadataPoint extends Component<MetadataPointProps, { isActive: boolean}> {
|
||||
|
||||
|
||||
constructor(props: MetadataPointProps) {
|
||||
super(props)
|
||||
this.state = {
|
||||
isActive: false
|
||||
}
|
||||
this.props.cue.onenter = this.onEnter.bind(this)
|
||||
this.props.cue.onexit = this.onExit.bind(this)
|
||||
this.onClick = this.onClick.bind(this)
|
||||
// get current theme (but in a class component)
|
||||
}
|
||||
|
||||
render() {
|
||||
const data = JSON.parse(this.props.cue.text)
|
||||
const titleAlt = data.title_alt
|
||||
? <h3 className="titleAlt">{data.title_alt}</h3>
|
||||
: ""
|
||||
const synopsis = data.synopsis
|
||||
? <div className="field">
|
||||
<span>Synopsis</span>
|
||||
{data.synopsis}
|
||||
</div>
|
||||
: ""
|
||||
const synopsisAlt = data.synopsis_alt
|
||||
? <div>{data.synopsis_alt}</div>
|
||||
: ""
|
||||
const keywords = data.keywords
|
||||
? <div className="field">
|
||||
<span>Keywords: </span>
|
||||
{data.keywords}
|
||||
</div>
|
||||
: ""
|
||||
const keywordsAlt = data.keywords_alt
|
||||
? <div className="field">
|
||||
<span>Alternative Keywords: </span>
|
||||
{data.keywords_alt}
|
||||
</div>
|
||||
: ""
|
||||
const subjects = data.subjects
|
||||
? <div className="field">
|
||||
<span>Subjects: </span>
|
||||
{data.subjects}
|
||||
</div>
|
||||
: ""
|
||||
const subjectsAlt = data.subjects_alt
|
||||
? <div className="field">
|
||||
<span>Alternative Subjects: </span>
|
||||
{data.subjects_alt}
|
||||
</div>
|
||||
: ""
|
||||
const gpsLink = data.gpspoints.gps
|
||||
? <div className="field">
|
||||
<span>Geo: </span>
|
||||
<a href={`https://www.google.com/maps/@?api=1&map_action=map¢er=${data.gpspoints.gps}&zoom=${data.gpspoints.gps_zoom}`}>{data.gpspoints.gps_text}</a>
|
||||
</div>
|
||||
: ""
|
||||
const hyperlinks = data.hyperlinks.hyperlink_text
|
||||
? <div className="field">
|
||||
<span>Links: </span>
|
||||
<a href={data.hyperlinks.hyperlink}>{data.hyperlinks.hyperlink_text}</a>
|
||||
</div>
|
||||
: ""
|
||||
let style = ''
|
||||
if (this.state.isActive) {
|
||||
// active
|
||||
style = "bg-gray-200"
|
||||
}
|
||||
// exect JSON.parse data to be of type MetadataPointData
|
||||
const point = JSON.parse(this.props.cue.text) as MetadataPointData
|
||||
const data = point.data
|
||||
|
||||
let song = null
|
||||
let footage = null
|
||||
|
||||
// get type of data
|
||||
if (point.type == "music") {
|
||||
// const song is point.data as MusicData
|
||||
song = data as MusicData
|
||||
}
|
||||
|
||||
if (point.type == "video_source") {
|
||||
footage = data as VideoSourceData
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="point">
|
||||
<div className={`point ${style}`}>
|
||||
<div className="time" onClick={this.onClick}>
|
||||
[{this.startTime()}]
|
||||
[{this.startTime()} - {this.endTime()}]
|
||||
</div>
|
||||
<div className="text">
|
||||
<h2 className="title" onClick={this.onClick}>{data.title}</h2>
|
||||
{titleAlt}
|
||||
{synopsis}
|
||||
{synopsisAlt}
|
||||
{keywords}
|
||||
{keywordsAlt}
|
||||
{subjects}
|
||||
{subjectsAlt}
|
||||
{gpsLink}
|
||||
{hyperlinks}
|
||||
{point.type == "music" &&
|
||||
<div className="music" onClick={this.onClick}>
|
||||
<SongCard song={song} />
|
||||
</div>
|
||||
}
|
||||
{point.type == "video_source" &&
|
||||
<div className="footage" onClick={this.onClick}>
|
||||
<VideoSourceCard videoSource={footage} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -94,6 +147,18 @@ class MetadataPoint extends Component<MetadataPointProps> {
|
|||
return this.formatSeconds(this.props.cue.startTime)
|
||||
}
|
||||
|
||||
endTime() {
|
||||
return this.formatSeconds(this.props.cue.endTime)
|
||||
}
|
||||
|
||||
onEnter() {
|
||||
this.setState({isActive: true})
|
||||
}
|
||||
|
||||
onExit() {
|
||||
this.setState({isActive: false})
|
||||
}
|
||||
|
||||
formatSeconds(t) {
|
||||
let mins = Math.floor(t / 60)
|
||||
if (mins < 10) {
|
||||
|
@ -110,4 +175,38 @@ class MetadataPoint extends Component<MetadataPointProps> {
|
|||
|
||||
}
|
||||
|
||||
const VideoSourceCard: React.FC<VideoSourceData> = ({videoSource}) => {
|
||||
return (
|
||||
<div className="bg-white shadow-md p-4">
|
||||
<h2 className="text-lg font-bold">{videoSource.title}</h2>
|
||||
<div className="text-gray-600">
|
||||
<p className="mb-1">{videoSource.artist}</p>
|
||||
<p className="mb-1">{videoSource.year}</p>
|
||||
{/* render the html that may be inside of {videoSource.notes} */}
|
||||
<div className="mb-1" dangerouslySetInnerHTML={{__html: videoSource.notes}} />
|
||||
</div>
|
||||
<a
|
||||
href={videoSource.hyperlink}
|
||||
className="top-0 right-0 bottom-0 bg-gray-500 hover:bg-gray-400 text-white text-xs font-bold px-3 py-1"
|
||||
>
|
||||
Link
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// TODO: fix typing
|
||||
const SongCard: React.FC<MusicData> = ({song}) => {
|
||||
return (
|
||||
<div className="bg-white shadow-md p-4">
|
||||
<h2 className="text-lg font-bold">{song.title}</h2>
|
||||
<div className="text-gray-600">
|
||||
<p className="mb-1">{song.artist}</p>
|
||||
<p className="mb-1">{song.year}</p>
|
||||
<p className="mb-1">{song.label}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MetadataPoint
|
|
@ -26,9 +26,16 @@ const Home: NextPage = () => {
|
|||
toggle theme
|
||||
</button>
|
||||
<div className="container flex flex-col items-center justify-center gap-12 px-4 py-16 ">
|
||||
<h1 className="text-1xl font-extrabold tracking-tight text-black dark:text-white sm:text-[3rem]">
|
||||
viewer
|
||||
</h1>
|
||||
{/* should hug upper-lefthand side */}
|
||||
{/* text in the upper-lefthand corner with some padding */}
|
||||
<div className="titles">
|
||||
<h1 className="text-1xl font-extrabold tracking-tight text-black dark:text-white sm:text-[3rem] top-4 left-4 absolute">
|
||||
documentary metadata viewer
|
||||
</h1>
|
||||
{/* hug the left-hand side of the screen */}
|
||||
<h2 className=" tracking-tight text-gray-500 dark:text-white top-20 left-6 absolute"><i>current film</i>: MIT: REGRESSIONS intro</h2>
|
||||
</div>
|
||||
|
||||
|
||||
<Player />
|
||||
|
||||
|
|
|
@ -69,12 +69,14 @@
|
|||
/* TranscriptLine */
|
||||
|
||||
.webvtt-player .active {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
/* background-color: #eee; */
|
||||
/* light border */
|
||||
@apply bg-gray-200 border-gray-600 border-2 border-opacity-10 border-x-0;
|
||||
}
|
||||
|
||||
.webvtt-player .match {
|
||||
background-color: lightyellow;
|
||||
/* background-color: lightyellow; */
|
||||
@apply bg-gray-200;
|
||||
}
|
||||
|
||||
.webvtt-player .line {
|
||||
|
|
Loading…
Reference in New Issue
Block a user