master
Giò Diani 2025-01-26 23:26:55 +01:00
parent f6b73be826
commit 44df18fcfb
15 changed files with 2189 additions and 2303 deletions

View File

@ -1 +0,0 @@
*,*:before,*:after{box-sizing:border-box}:root{--c1: #F2F2F2;--c2: #D9D9D9;--c3: #737373;--c4: #404040;--c5: #0D0D0D}html{-moz-text-size-adjust:none;-webkit-text-size-adjust:none;-webkit-font-smoothing:antialiased;text-size-adjust:none}*{margin:0}ul[role=list],ol[role=list]{list-style:none}body{min-height:100vh;line-height:1.5;padding:1.5em;font-family:sans-serif;background:var(--c4);color:var(--c1)}img,picture,video,canvas,svg{display:block;max-width:100%}input,button,textarea,select{font:inherit}input{margin-top:.2em;border-radius:.2em;border:1px solid #fff;background:var(--c3);color:#fff;padding:.2em .5em;display:block}h1,h2,h3,h4,button,input,label{line-height:1.1}fieldset{display:block;border:none;padding:0;margin:0}h1,h2,h3,h4{text-wrap:balance}a:not([class]){text-decoration-skip-ink:auto;color:currentColor}input,button,textarea,select{font-family:inherit;font-size:inherit}textarea:not([rows]){min-height:10em}:target{scroll-margin-block:5ex}dl{display:grid;grid-template-areas:"title desc";gap:.5em 1em}dt{font-weight:600}.visually-hidden{position:absolute;left:-10000px;top:auto;width:1px;height:1px;overflow:hidden}.tablist{height:100%;display:grid;grid-template:"tablist" "panel";grid-template-rows:2.5em 1fr}.controls{background:var(--c5)}.control{display:inline-block}.control label{display:inline-grid;justify-items:center;align-items:center;padding:.5em 1em;height:2.5em;color:var(--c1)}input[type=radio]:checked+label{background:var(--c3)}a:focus,input:focus,input[type=radio]:focus+label{outline:2px dotted;outline-offset:2px}input[type=radio]+label:hover{cursor:pointer;text-decoration:underline}.panel{background:var(--c3);padding:1em;height:calc(50vh - 4em);overflow-y:scroll}#tablist_panel_transcript_panel font{display:block;color:#fff!important}#transcript{padding:0;list-style:none}#transcript a{cursor:pointer}#transcript li{display:flex}#transcript li>div{margin-right:1em}.vjs-title-bar{display:none!important}header{position:fixed;top:0;left:0;width:100%;background:#000;z-index:99;height:2.5em;display:flex;align-items:center;padding:0 2em}header a{text-decoration:none}header a:before{content:"←";padding:0 .5em 0 0}main{width:100%;margin-top:1.5em;height:calc(100vh - 6.5em);display:grid;grid-template-areas:"video tabs" "timeline timeline";grid-template-columns:repeat(2,1fr);grid-template-rows:1fr 50%;gap:1em}ol{padding-left:1em}#video{grid-area:video}#tabs{grid-area:tabs}#timeline{grid-area:timeline;position:relative;background:var(--c3)}#timeline-bar{height:2em;margin-left:20em;background:#aaa;display:flex;justify-content:space-between}#timeline-bar>span{display:inline-flex;align-items:center}#timeline-bar-ctrl{display:inline-block;background:#000;width:.1875em;height:100%;position:absolute;z-index:999}#timeline-bar-ctrl:before{content:"";width:2em;height:2em;border-radius:50%;transform:translate3d(calc(-50% + .09375em),0,0);position:absolute;background:#000}#tracks{height:calc(100% - 2em);overflow-y:scroll}.track{width:100%;min-height:15em;display:grid;grid-template:100% / 20em 1fr}.track+.track{border-top:1px solid var(--c1)}.track-ctrl{padding:1em;background:var(--c3)}.track-ctrl h2{font-size:1.2em}.track-ctrl p{margin-top:1em;font-size:1em;line-height:1.5}.track-ctrl label{margin-top:1em;display:inline-block}.track-viz{background:var(--c3)}ul.segments{padding:0;grid-template:1fr / repeat(4,1fr);list-style:none;display:grid;grid-template:;height:100%}ul.segments li{background:#ccc;color:#000;border-left:.0625em solid #fff;padding:1em}.track-viz svg{width:100%!important}#sentiments-list{list-style:none;padding:0}#sentiments-list li{margin-top:.5em}#sentiments-list span{display:inline-block;background:#d01c8b;width:1.5em;height:1.5em;border-radius:50%;text-align:center}#sentiments-list span.pos{background:#4dac26}#sentiments-list span.neut{background:#f7f7f7;color:#000}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
*,*:before,*:after{box-sizing:border-box}:root{--c1: #F2F2F2;--c2: #D9D9D9;--c3: #737373;--c4: #404040;--c5: #0D0D0D}html{-moz-text-size-adjust:none;-webkit-text-size-adjust:none;-webkit-font-smoothing:antialiased;text-size-adjust:none}*{margin:0}ul[role=list],ol[role=list]{list-style:none}body{min-height:100vh;line-height:1.5;padding:1.5em;font-family:sans-serif;background:var(--c4);color:var(--c1)}img,picture,video,canvas,svg{display:block;max-width:100%}input,button,textarea,select{font:inherit}button,input{margin-top:.2em;border-radius:.2em;border:1px solid #fff;background:var(--c3);color:#fff;padding:.2em .5em;display:block}.searchform button{border-radius:0 .2em .2em 0}button:hover{background:#fff;color:#000}.searchform input{border-radius:.2em 0 0 .2em;border-right:0}h1,h2,h3,h4,button,input,label{line-height:1.1}fieldset{display:block;border:none;padding:0;margin:0}h1,h2,h3,h4{text-wrap:balance}a:not([class]){text-decoration-skip-ink:auto;color:currentColor}input,button,textarea,select{font-family:inherit;font-size:inherit}textarea:not([rows]){min-height:10em}:target{scroll-margin-block:5ex}dl{display:grid;grid-template-areas:"title desc";gap:.5em 1em}dt{font-weight:600}.visually-hidden{position:absolute;left:-10000px;top:auto;width:1px;height:1px;overflow:hidden}.tablist{height:100%;display:grid;grid-template:"tablist" "panel";grid-template-rows:2.5em 1fr}.controls{background:var(--c5)}.control{display:inline-block}.control label{display:inline-grid;justify-items:center;align-items:center;padding:.5em 1em;height:2.5em;color:var(--c1)}input[type=radio]:checked+label{background:var(--c3)}a:focus,input:focus,input[type=radio]:focus+label{outline:2px dotted;outline-offset:2px}input[type=radio]+label:hover{cursor:pointer;text-decoration:underline}.panel{background:var(--c3);padding:1em;height:calc(50vh - 4em);overflow-y:scroll}#tablist_panel_transcript_panel font,#tablist_panel_search_panel font{display:block;color:#fff!important}.searchform{display:flex}#searchresults{margin-top:1em}#searchresults,#transcript{padding:0;list-style:none}#searchresults a,#transcript a{cursor:pointer}#searchresults li,#transcript li{display:flex}#searchresults li>div,#transcript li>div{margin-right:1em}.vjs-title-bar{display:none!important}#topicslist a{background:#fff;color:#000;cursor:pointer;padding:.2em;border-radius:.3em;display:inline-block;margin-top:.3em}header{position:fixed;top:0;left:0;width:100%;background:#000;z-index:99;height:2.5em;display:flex;align-items:center;padding:0 2em}header a{text-decoration:none}header a:before{content:"←";padding:0 .5em 0 0}main{width:100%;margin-top:1.5em;height:calc(100vh - 6.5em);display:grid;grid-template-areas:"video tabs" "timeline timeline";grid-template-columns:repeat(2,1fr);grid-template-rows:1fr 50%;gap:1em}ol{padding-left:1em}#video{grid-area:video}#tabs{grid-area:tabs}#timeline{grid-area:timeline;position:relative;background:var(--c3)}#timeline-bar{height:2em;margin-left:20em;background:#aaa;display:flex;justify-content:space-between}#timeline-bar>span{display:inline-flex;align-items:center}#timeline-bar-ctrl{display:inline-block;background:#000;width:.1875em;height:100%;position:absolute;z-index:999}#timeline-bar-ctrl:before{content:"";width:2em;height:2em;border-radius:50%;transform:translate3d(calc(-50% + .09375em),0,0);position:absolute;background:#000}#tracks{height:calc(100% - 2em);overflow-y:scroll}.track{width:100%;min-height:15em;display:grid;grid-template:100% / 20em 1fr}.track+.track{border-top:1px solid var(--c1)}.track-ctrl{padding:1em;background:var(--c3)}.track-ctrl h2{font-size:1.2em}.track-ctrl p{margin-top:1em;font-size:1em;line-height:1.5}.track-ctrl label{margin-top:1em;display:inline-block}.track-viz{background:var(--c3)}ul.segments{padding:0;grid-template:1fr / repeat(4,1fr);list-style:none;display:grid;grid-template:;height:100%}ul.segments li{background:#ccc;color:#000;border-left:.0625em solid #fff;padding:1em}.track-viz svg{width:100%!important}#sentiments-list{list-style:none;padding:0}#sentiments-list li{margin-top:.5em}#sentiments-list span{display:inline-block;background:#d01c8b;width:1.5em;height:1.5em;border-radius:50%;text-align:center}#sentiments-list span.pos{background:#4dac26}#sentiments-list span.neut{background:#f7f7f7;color:#000}

View File

@ -5,12 +5,12 @@
"isEntry": true
},
"resources/css/app.css": {
"file": "assets/app-BDv93WNO.css",
"file": "assets/app-DQLXVmOM.css",
"src": "resources/css/app.css",
"isEntry": true
},
"resources/js/app.js": {
"file": "assets/app-CrWEnB8o.js",
"file": "assets/app-DGnSPD5n.js",
"name": "app",
"src": "resources/js/app.js",
"isEntry": true

View File

@ -46,7 +46,7 @@ img, picture, video, canvas, svg {
input, button, textarea, select {
font: inherit;
}
button,
input{
margin-top: .2em;
border-radius: .2em;
@ -57,6 +57,21 @@ input{
display: block;
}
.searchform button{
border-radius: 0 .2em .2em 0;
}
button:hover{
background: #fff;
color: #000;
}
.searchform input{
border-radius: .2em 0 0 .2em;
border-right: 0;
}
h1,
h2,
@ -175,24 +190,37 @@ input[type="radio"] + label:hover {
overflow-y: scroll;
}
#tablist_panel_transcript_panel font{
#tablist_panel_transcript_panel font,
#tablist_panel_search_panel font{
display: block;
color: #fff !important
}
.searchform{
display: flex;
}
#searchresults{
margin-top: 1em;
}
#searchresults,
#transcript{
padding: 0;
list-style: none;
}
#searchresults a,
#transcript a{
cursor: pointer;
}
#searchresults li,
#transcript li{
display: flex;
}
#searchresults li>div,
#transcript li>div{
margin-right: 1em;
}
@ -201,6 +229,15 @@ input[type="radio"] + label:hover {
display: none !important;
}
#topicslist a{
background: #fff;
color: #000;
cursor: pointer;
padding: .2em;
border-radius: .3em;
display: inline-block;
margin-top: .3em;
}
/* Layout */
header{
@ -374,3 +411,5 @@ ul.segments li{
background: #f7f7f7;
color: #000;
}

View File

@ -6,10 +6,12 @@ import { gsap } from "gsap";
import { Draggable } from "gsap/Draggable";
import * as echarts from 'echarts';
window.echarts = echarts;
// Tabs
function changeTab(el){
let tabId = el.target.getAttribute('id');
let tabId = el.getAttribute('id');
let panels = document.querySelectorAll('.panel');
panels.forEach(p => {
@ -24,7 +26,7 @@ function changeTab(el){
const tabs = document.querySelectorAll('input[type="radio"]');
tabs.forEach(tab => {
tab.addEventListener('click', e => {
changeTab(e);
changeTab(e.target);
})
})
@ -47,8 +49,6 @@ function setWordCountChart(){
// let max = totalWordCount / count;
let data = [];
let wordWordsLen = wordWords.length;
let chunks = Math.floor(wordWordsLen / count);
@ -119,14 +119,26 @@ function setWordCountChart(){
}
const wordCountChartOptions = {
tooltip: {
trigger: 'axis',
valueFormatter: (value) => 'Gesprochene Worte ' + new Intl.NumberFormat('de-CH').format(value)
},
grid: grid,
xAxis: xAxis,
yAxis: yAxis,
series: series
series: series,
tooltip: {
trigger: 'axis',
valueFormatter: (value) => 'Gesprochene Worte ' + new Intl.NumberFormat('de-CH').format(value),
formatter: (v) => {
let index = v[0].componentIndex;
//console.log(wordCountChartOptions.series[0]);
let count_tooltip = 0;
for(let i = 0; i < index; i++){
count_tooltip = count_tooltip + parseInt(wordCountChartOptions.series[i].data.slice(-1));
}
count_tooltip = parseInt(v[0].axisValue) + count_tooltip;
return `Segment: ${index + 1}<br>Wort: ${count_tooltip}`;
}
},
}
@ -152,7 +164,12 @@ const chartSentimentsOptions = {
tooltip: {
trigger: 'axis',
position: "top",
formatter: 'Sekunde: {b0}<br>Sentiment: {c0}'
formatter: (e) => {
let sek = e[0].axisValue;
let sent = e[0].data;
sent = sent == 0 ? 'Neutral' : sent == 1 ? 'Positiv' : 'Negativ';
return `Sekunde: ${sek}<br>Sentiment: ${sent}`
}
},
grid: {
show: false,
@ -191,6 +208,7 @@ const chartSentimentsOptions = {
chartSentiments.setOption(chartSentimentsOptions);
// VIDEO
@ -201,7 +219,7 @@ gsap.registerPlugin(Draggable);
const player = new Pillarbox('my-player', {
controls: false,
muted: true,
muted: false,
srgOptions: {
liveui: false
}
@ -288,3 +306,62 @@ document.addEventListener("DOMContentLoaded", (event) => {
},
});
});
chartSentiments.on('click', 'series', (e) => {
player.currentTime(chartSentimentsOptions.xAxis.data[e.dataIndex]);
player.play();
});
// Suche
const searchInput = document.querySelector('#search')
const searchInputBtn = document.querySelector('#searchbutton')
const searchresults = document.querySelector('#searchresults')
function search(term){
if(term.length > 1){
searchresults.innerHTML = '';
transcript.querySelectorAll('li').forEach((el)=>{
let text = el.querySelector('font').innerText.toLowerCase();
term = term.toLowerCase();
if(text.includes(term)){
let clone = el.cloneNode(true);
searchresults.appendChild(clone);
}
})
if(!searchresults.querySelector('li')){
searchresults.innerHTML = `<li>Keine Ergebnisse für «${term}»</li>`;
}else{
searchresults.querySelectorAll('[data-start]').forEach(c => {
c.addEventListener('click', (e) => {
player.currentTime(e.target.getAttribute('data-start'));
player.play();
});
});
}
}else{
searchresults.innerHTML = `<li>Bitte gib mind. 2 Buchstaben ein.</li>`;
}
}
searchInputBtn.addEventListener('click', () => {
search(searchInput.value)
})
const topics = document.querySelectorAll('#topicslist a');
topics.forEach(el => {
el.addEventListener('click', e => {
search(e.target.innerText);
searchInput.value = e.target.innerText;
let tab = document.getElementById('tablist_panel_search');
tab.checked = true
changeTab(document.getElementById('tablist_panel_search'));
})
})

View File

@ -33,6 +33,11 @@
for="tablist_panel_terms"><span class="visually-hidden">Zeige Tabinhalt
</span>Topics</label>
</div>
<div class="control">
<input class="visually-hidden" id="tablist_panel_search" name="tablist" type="radio" /><label
for="tablist_panel_search"><span class="visually-hidden">Zeige Tabinhalt
</span>Suche</label>
</div>
</fieldset>
<div class="panel" id="tablist_panel_episode_panel">
<h2 class="visually-hidden">
@ -71,15 +76,27 @@
<h2 class="visually-hidden">
Topics (Tabinhalt)
</h2>
<ol>
@foreach($topics as $topic)
<ol id="topicslist">
@foreach($topics['topics'] as $topic)
<li>
{{ implode(', ', $topic) }}
@foreach($topic as $t)
<a title="Suche nach {{ $t }}">{{ $t }}</a>
@endforeach
</li>
@endforeach
</ol>
</div>
<div class="panel" id="tablist_panel_search_panel" hidden>
<h2 class="visually-hidden">
Suche (Tabinhalt)
</h2>
<div class="searchform">
<input type="text" id="search">
<button id="searchbutton">Suche</button>
</div>
<ol id="searchresults"></ol>
</div>
</div>
</div>
@ -120,9 +137,13 @@
<li><span class="neut">0</span> = Neutral ({{ round($senti_subdata['verteilung_perc']['neutral']) }}%)</li >
<li><span class="neg">-1</span> = Negativ ({{ round($senti_subdata['verteilung_perc']['negative']) }}%)</li></p>
</div>
<div class="track-viz" id="sentiment-track" data-time="{{ implode(',', $senti_subdata['time']) }}" data-sentiments="{{ $senti_subdata['data'] }}" data-weights="{{ $senti_subdata['weights'] }}"></div>
<div
class="track-viz"
id="sentiment-track"
data-time="{{ implode(',', $senti_subdata['time']) }}" data-sentiments="{{ $senti_subdata['data'] }}" data-weights="{{ $senti_subdata['weights'] }}"></div>
</div>
</div>
</div>
</main>
@endsection

View File

@ -24,8 +24,7 @@ Route::get('/detail/{id}', function(int $id) {
$subtitles = $ep->subtitles;
$mediacomposition = json_decode($ep->mediacomposition, 1);
$durationSteps = $mediacomposition['chapterList'][0]['duration'] / 1000 / 10;
$topics = json_decode($ep->topics);
$topics = json_decode($ep->topics, 1);
$subdata = json_decode($ep->subtitle_data, 1);
$subdata_senti = json_decode($ep->sentiments_from_sub, 1);
@ -34,7 +33,6 @@ Route::get('/detail/{id}', function(int $id) {
$subdata_senti['weights'] = "";
$subdata_senti['verteilung'] = ['neutral' => 0, 'negative' => 0, 'positive' => 0];
foreach ($subdata_senti['sentiments'] as $value) {
$subdata_senti['data'] .= $value[0].",";
$subdata_senti['weights'] .= (50 * $value[1]).",";
@ -57,7 +55,6 @@ Route::get('/detail/{id}', function(int $id) {
$parser = new Podlove\Webvtt\Parser();
$subtitles = $parser->parse($subtitles);
return view('detail', ['eps' => $eps, 'title' => $title, 'subtitles' => $subtitles, 'mediacomposition' => $mediacomposition, 'durationSteps' => $durationSteps, 'dom_color' => $ep->viz_data, 'subdata' => $subdata, 'senti_subdata' => $subdata_senti, 'wpm' => $wordsPerMinute, 'topics' => $topics]);
});

View File

@ -1,6 +1,8 @@
import sqlite3
from pathlib import Path
con = sqlite3.connect("/home/gio/Code/VANA/database.sqlite")
db_path = str(Path(__file__).parents[4]) + '/database.sqlite'
con = sqlite3.connect(db_path)
cur = con.cursor()
def get_subtitle(id):
@ -40,5 +42,3 @@ def save_topics(id, data):
cur.execute("UPDATE episodes SET topics = ? WHERE id = ?", [data, id])
con.commit()
con.close()

View File

@ -6,7 +6,7 @@ from pathlib import Path
import polars as pl
from database import queries
from germansentiment import SentimentModel
from import SentimentModel
def get_sent(ep):

View File

@ -2,6 +2,7 @@
import argparse
import json
import re
from pathlib import Path
import polars as pl
@ -46,8 +47,24 @@ def get_topics(ep):
top.append(word[0])
topics_list.append(top)
return json.dumps(topics_list)
output_list = []
for patterns in topics_list:
patterns = [re.sub(r'\.', '\.', p) for p in patterns]
patterns = [re.sub(r'\*', '\*', p) for p in patterns]
patterns = '|'.join(patterns)
df = df.with_columns(pl.col("sentences").str.extract_all(patterns).alias("matches"))
output = {"timecode" : [], "matches" : []}
for row in df.rows(named=True):
if len(row['matches']) > 0:
output["timecode"].append(row["start"])
output["matches"].append(row["matches"])
output_list.append(output)
return json.dumps({"topics" : topics_list, "verteilung" : output_list})
# CLI
parser = argparse.ArgumentParser(

File diff suppressed because it is too large Load Diff

Binary file not shown.