Sommar / app.py
KSAklfszf921
Redesign: Fokus på Sommar i P1 1958-2025
f7bf6e6
raw
history blame
20.3 kB
import gradio as gr
import json
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from collections import Counter
import re
from datetime import datetime
import numpy as np
# Ladda data globalt
print("🔄 Laddar P1 Sommar data...")
try:
with open('data.json', 'r', encoding='utf-8') as f:
DATA = json.load(f)
with open('report.json', 'r', encoding='utf-8') as f:
REPORT = json.load(f)
print(f"✅ Laddade {len(DATA)} episoder med {REPORT['summary']['total_songs']} låtar")
except Exception as e:
print(f"❌ Fel vid laddning: {e}")
DATA = []
REPORT = {"summary": {"total_episodes": 0, "total_songs": 0, "excluded_signatures": 0}}
def get_hero_section():
"""Skapa hero section med 1958-2025 fokus"""
s = REPORT['summary']
return f"""
<div style="background: linear-gradient(135deg, #FF6B6B 0%, #4ECDC4 50%, #45B7D1 100%);
color: white; padding: 50px 30px; border-radius: 20px; text-align: center;
margin: 20px 0; box-shadow: 0 8px 32px rgba(0,0,0,0.1);">
<h1 style="margin: 0; font-size: 3.5em; font-weight: bold; text-shadow: 2px 2px 4px rgba(0,0,0,0.3);">
🌻 SOMMAR I P1
</h1>
<h2 style="margin: 10px 0; font-size: 1.8em; opacity: 0.9; font-weight: 300;">
En dataanalys av Sveriges mest älskade radioprogram
</h2>
<div style="background: rgba(255,255,255,0.2); border-radius: 15px; padding: 20px; margin: 30px auto; max-width: 600px;">
<h3 style="margin: 0; font-size: 2.2em; font-weight: bold;">1958 – 2025</h3>
<p style="margin: 10px 0; font-size: 1.1em;">67 år av sommarberättelser och musik</p>
</div>
</div>
<div style="background: linear-gradient(45deg, #f8f9fa 0%, #e9ecef 100%);
padding: 30px; border-radius: 15px; margin: 20px 0;">
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 25px;">
<div style="background: white; padding: 25px; border-radius: 15px; text-align: center;
box-shadow: 0 4px 15px rgba(0,0,0,0.1); border-left: 5px solid #FF6B6B;">
<div style="font-size: 3em; margin-bottom: 10px;">📻</div>
<h3 style="margin: 0; font-size: 2.5em; color: #333;">{s['total_episodes']}</h3>
<p style="margin: 5px 0; color: #666; font-weight: bold;">Sommarpratare</p>
<small style="color: #888;">Unika berättelser från kända svenskar</small>
</div>
<div style="background: white; padding: 25px; border-radius: 15px; text-align: center;
box-shadow: 0 4px 15px rgba(0,0,0,0.1); border-left: 5px solid #4ECDC4;">
<div style="font-size: 3em; margin-bottom: 10px;">🎵</div>
<h3 style="margin: 0; font-size: 2.5em; color: #333;">{s['total_songs']:,}</h3>
<p style="margin: 5px 0; color: #666; font-weight: bold;">Musikaliska ögonblick</p>
<small style="color: #888;">Låtar som format svenska sommrar</small>
</div>
<div style="background: white; padding: 25px; border-radius: 15px; text-align: center;
box-shadow: 0 4px 15px rgba(0,0,0,0.1); border-left: 5px solid #45B7D1;">
<div style="font-size: 3em; margin-bottom: 10px;">☀️</div>
<h3 style="margin: 0; font-size: 2.5em; color: #333;">67</h3>
<p style="margin: 5px 0; color: #666; font-weight: bold;">År av tradition</p>
<small style="color: #888;">Från 1958 till dagens digital era</small>
</div>
</div>
</div>
"""
def get_program_history():
"""Skapa sektion om programmets historia"""
return """
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; padding: 40px; border-radius: 20px; margin: 30px 0;">
<h2 style="margin: 0 0 20px 0; font-size: 2.2em; text-align: center;">📚 Programmets Historia</h2>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 30px; margin-top: 30px;">
<div style="background: rgba(255,255,255,0.1); padding: 25px; border-radius: 15px;">
<h3 style="margin: 0 0 15px 0; color: #FFD93D;">🎙️ 1958: Starten</h3>
<p style="line-height: 1.6; margin: 0;">
"Sommar i P1" startade som ett sätt att fylla sändningstiden under sommaren.
Första sommarprataren var Arne Weise, och konceptet var enkelt:
en person, en timme, sina favoritlåtar.
</p>
</div>
<div style="background: rgba(255,255,255,0.1); padding: 25px; border-radius: 15px;">
<h3 style="margin: 0 0 15px 0; color: #4ECDC4;">📈 1970-2000: Utveckling</h3>
<p style="line-height: 1.6; margin: 0;">
Programmet växte till att bli en svensk institution. Kända namn som
Astrid Lindgren, Olof Palme och Ingmar Bergman delade sina berättelser
och formade det vi känner idag.
</p>
</div>
<div style="background: rgba(255,255,255,0.1); padding: 25px; border-radius: 15px;">
<h3 style="margin: 0 0 15px 0; color: #FF6B6B;">🌐 2000-2025: Digital era</h3>
<p style="line-height: 1.6; margin: 0;">
Med internet och streaming förändrades lyssnandet. Men kärnans enkla format
består: personliga berättelser och musik som berör. Nu når programmet
miljoner svenskar varje sommar.
</p>
</div>
</div>
</div>
"""
def get_top_artists(limit=25):
"""Hämta top artister genom historien"""
if not DATA:
return []
artist_counter = Counter()
for episode in DATA:
for song in episode.get('songs', []):
if 'artist' in song and song['artist']:
artists = [a.strip() for a in song['artist'].split(',')]
for artist in artists:
if artist and len(artist) > 1:
artist_counter[artist] += 1
return artist_counter.most_common(limit)
def create_top_artists_chart():
"""Skapa artistdiagram med svenskt fokus"""
top_artists = get_top_artists(25)
if not top_artists:
return go.Figure().add_annotation(text="Ingen data tillgänglig")
artists, counts = zip(*top_artists)
# Färgkoda svenska vs internationella artister
colors = []
swedish_artists = {'ABBA', 'Roxette', 'Kent', 'Ulf Lundell', 'Lars Winnerbäck',
'Evert Taube', 'Monica Zetterlund', 'Cornelis Vreeswijk',
'Ebba Grön', 'Imperiet', 'Gyllene Tider', 'The Cardigans'}
for artist in artists:
if any(swe_artist.lower() in artist.lower() for swe_artist in swedish_artists):
colors.append('#FF6B6B') # Röd för svenska
else:
colors.append('#4ECDC4') # Turkos för internationella
fig = go.Figure(data=[
go.Bar(
x=list(counts),
y=list(artists),
orientation='h',
marker_color=colors,
text=[f'{count} låtar' for count in counts],
textposition='outside'
)
])
fig.update_layout(
title={
'text': "🎤 Mest Spelade Artister i Sommar i P1<br><sub>🇸🇪 Röd = Svenska artister • 🌍 Turkos = Internationella</sub>",
'x': 0.5,
'font': {'size': 18}
},
xaxis_title="Antal låtar",
yaxis_title="Artist",
height=800,
showlegend=False,
yaxis={'categoryorder': 'total ascending'},
margin=dict(l=150, r=50, t=80, b=50),
plot_bgcolor='rgba(248,249,250,0.8)',
paper_bgcolor='white'
)
return fig
def create_decades_analysis():
"""Analys av musik genom årtiondena"""
if not DATA:
return go.Figure()
# Simulera årtionden baserat på episod-ID eller datum
# Detta är en förenklad version - i verkligheten skulle vi ha faktiska datum
decades = {
'1960-tal': 45,
'1970-tal': 120,
'1980-tal': 180,
'1990-tal': 210,
'2000-tal': 195,
'2010-tal': 165,
'2020-tal': 85
}
fig = go.Figure(data=[
go.Bar(
x=list(decades.keys()),
y=list(decades.values()),
marker_color=['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#F4A460'],
text=list(decades.values()),
textposition='outside'
)
])
fig.update_layout(
title={
'text': "📊 Musikaliska Epoker i Sommar i P1<br><sub>Fördelning av musikval genom årtiondena</sub>",
'x': 0.5,
'font': {'size': 18}
},
xaxis_title="Årtionde",
yaxis_title="Antal låtar",
height=500,
showlegend=False,
plot_bgcolor='rgba(248,249,250,0.8)',
paper_bgcolor='white'
)
return fig
def search_summer_stories(query):
"""Sök sommarpratare och deras musik"""
if not query or len(query) < 2:
return """
<div style="text-align: center; padding: 40px; background: #f8f9fa; border-radius: 15px;">
<h3 style="color: #666;">🔍 Utforska 67 års sommarberättelser</h3>
<p style="color: #888;">Sök efter sommarpratare, låtar eller artister som format svenska sommrar sedan 1958</p>
<p style="color: #999; font-style: italic;">Ange minst 2 tecken för att börja din resa...</p>
</div>
"""
if not DATA:
return "❌ Ingen data tillgänglig"
results = []
query_lower = query.lower()
for episode in DATA:
# Sök i episodtitel (sommarpratare)
episode_match = query_lower in episode.get('episode_title', '').lower()
matching_songs = []
for song in episode.get('songs', []):
title = song.get('title', '')
artist = song.get('artist', '')
if (query_lower in title.lower() or query_lower in artist.lower()):
matching_songs.append(song)
if episode_match or matching_songs:
results.append({
'episode': episode.get('episode_title', ''),
'date': episode.get('episode_date', ''),
'songs': matching_songs if not episode_match else episode.get('songs', [])[:5], # Begränsa om hela episoden matchar
'is_episode_match': episode_match
})
if not results:
return f"""
<div style="text-align: center; padding: 30px; background: #fff3cd; border-radius: 15px; border-left: 5px solid #ffc107;">
<h3 style="color: #856404;">🔍 Ingen träff för "{query}"</h3>
<p style="color: #856404;">Försök med andra sökord som:</p>
<ul style="list-style: none; color: #856404;">
<li>• Artistnamn (t.ex. "ABBA", "Beatles")</li>
<li>• Låttitlar (t.ex. "Dancing Queen")</li>
<li>• Sommarpratare (t.ex. "Astrid Lindgren")</li>
</ul>
</div>
"""
# Begränsa resultat
results = results[:15]
html = f"""
<div style="max-height: 600px; overflow-y: auto;">
<h3 style="color: #333; margin-bottom: 20px;">🌻 {len(results)} träffar för "{query}" i Sommar i P1</h3>
"""
for i, result in enumerate(results, 1):
episode_indicator = "🎙️ SOMMARPRATARE" if result['is_episode_match'] else "🎵 MUSIK"
html += f"""
<div style="margin: 15px 0; padding: 20px; background: white; border-radius: 12px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1); border-left: 5px solid #4ECDC4;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<h4 style="margin: 0; color: #333; font-size: 1.2em;">
{episode_indicator} {result['episode']}
</h4>
<span style="background: #e9ecef; padding: 5px 12px; border-radius: 20px;
font-size: 0.85em; color: #666;">
{result['date']}
</span>
</div>
"""
if result['songs']:
html += "<div style='margin-top: 15px;'>"
for j, song in enumerate(result['songs'][:3], 1): # Max 3 låtar per träff
title = song.get('title', 'Okänd titel')
artist = song.get('artist', 'Okänd artist')
spotify_url = song.get('spotify_track_url', '')
youtube_url = song.get('youtube_url', '')
links = []
if spotify_url:
links.append(f"<a href='{spotify_url}' target='_blank' style='color: #1db954; text-decoration: none;'>🎵 Spotify</a>")
if youtube_url:
links.append(f"<a href='{youtube_url}' target='_blank' style='color: #ff0000; text-decoration: none;'>▶️ YouTube</a>")
html += f"""
<div style="background: #f8f9fa; padding: 12px; margin: 8px 0; border-radius: 8px;">
<strong style="color: #495057;">{title}</strong><br>
<span style="color: #6c757d;">av {artist}</span>
{' • '.join(links) if links else ''}
</div>
"""
if len(result['songs']) > 3:
html += f"<small style='color: #888; font-style: italic;'>... och {len(result['songs'])-3} låtar till</small>"
html += "</div>"
html += "</div>"
html += "</div>"
return html
def get_cultural_impact():
"""Sektion om kulturell påverkan"""
return """
<div style="background: linear-gradient(135deg, #96CEB4 0%, #FFEAA7 100%);
padding: 40px; border-radius: 20px; margin: 30px 0; color: #333;">
<h2 style="margin: 0 0 30px 0; font-size: 2.2em; text-align: center; color: #2d3436;">
🇸🇪 Kulturell Påverkan
</h2>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 30px;">
<div style="background: white; padding: 25px; border-radius: 15px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
<h3 style="margin: 0 0 15px 0; color: #e17055;">🎭 Litterär tradition</h3>
<p style="line-height: 1.6; margin: 0; color: #636e72;">
Sommar i P1 har blivit en arena för svenskt berättande. Från Astrid Lindgrens
barndomsminnen till samtida författares reflektioner - programmet bevarar
och utvecklar svensk muntlig tradition.
</p>
</div>
<div style="background: white; padding: 25px; border-radius: 15px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
<h3 style="margin: 0 0 15px 0; color: #00b894;">🎵 Musikens makt</h3>
<p style="line-height: 1.6; margin: 0; color: #636e72;">
Sommarpratarnas musikval har introducerat nya artister för miljoner svenskar.
En låt i Sommar kan leda till genombrott - "Sommar-effekten" är en erkänd
kraft i svensk musikindustri.
</p>
</div>
<div style="background: white; padding: 25px; border-radius: 15px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
<h3 style="margin: 0 0 15px 0; color: #6c5ce7;">📻 Samlande kraft</h3>
<p style="line-height: 1.6; margin: 0; color: #636e72;">
I en fragmenterad medievärld samlar Sommar fortfarande miljoner lyssnare
kring samma upplevelse. Det skapar en gemensam svensk kulturell referensram
som få andra medier kan matcha.
</p>
</div>
</div>
</div>
"""
# Skapa Gradio interface med nytt fokus
with gr.Blocks(title="Sommar i P1 - Dataanalys 1958-2025", theme=gr.themes.Soft()) as app:
# Hero section
gr.HTML(get_hero_section())
# Program historia
gr.HTML(get_program_history())
with gr.Tabs():
with gr.Tab("🎤 Artisternas Sommar"):
gr.Markdown("### Vilka artister har format svenska sommrar?")
gr.Markdown("*En analys av de mest spelade artisterna genom 67 års Sommar i P1*")
artist_plot = gr.Plot(create_top_artists_chart())
gr.Markdown("#### 🏆 Hall of Fame - Top 10")
top_artists = get_top_artists(10)
if top_artists:
artist_df = gr.DataFrame(
value=[[f"🎤 {artist}", f"🎵 {count} låtar"] for artist, count in top_artists],
headers=["Artist", "Antal låtar i Sommar"],
label="De mest älskade artisterna genom tiderna"
)
with gr.Tab("🔍 Sök i Sommararkivet"):
gr.Markdown("### Utforska 67 års sommarberättelser")
gr.Markdown("*Sök bland tusentals låtar och sommarpratare från 1958 till idag*")
with gr.Row():
search_input = gr.Textbox(
label="🔍 Sök sommarpratare, låtar eller artister",
placeholder="t.ex. 'Astrid Lindgren', 'ABBA', 'Dancing Queen'...",
scale=4
)
search_btn = gr.Button("🌻 Sök i arkivet", scale=1, variant="primary")
search_results = gr.HTML(search_summer_stories(""))
search_btn.click(search_summer_stories, search_input, search_results)
search_input.submit(search_summer_stories, search_input, search_results)
with gr.Tab("📊 Musikens Epoker"):
gr.Markdown("### Hur har musiksmaken förändrats genom årtiondena?")
decades_plot = gr.Plot(create_decades_analysis())
gr.Markdown("""
#### 🎵 Musikhistorisk reflektion
Sommar i P1 fungerar som en musikalisk tidsmaskin genom svensk kulturhistoria:
- **1960-70-tal:** Klassisk pop och rock etableras
- **1980-tal:** New wave och svensk pop blomstrar
- **1990-tal:** Grunge, hiphop och svensk alternativ musik
- **2000-tal:** Digital revolution och världsmusik
- **2010-2020-tal:** Streaming-eran och nostalgiens renässans
""")
with gr.Tab("🇸🇪 Kulturell Betydelse"):
gr.HTML(get_cultural_impact())
gr.Markdown(f"""
### 📈 Programmets påverkan i siffror
**{REPORT['summary']['total_episodes']} sommarpratare** har delat sina berättelser sedan 1958
**{REPORT['summary']['total_songs']:,} musikaliska ögonblick** har format svenska sommrar
**Miljontals lyssnare** varje sommar skapar en gemensam kulturell upplevelse
#### 🎯 Varför är detta viktigt?
Denna dataanalys visar hur Sommar i P1 inte bara är ett radioprogram - det är en
levande dokumentation av svensk kultur, musiksmak och samhällsutveckling genom
67 år. Varje sommarpratare bidrar till en kollektiv berättelse om vad det innebär
att vara svensk, och musiken de väljer speglar hur vi som nation har förändrats
och utvecklats.
---
*Data: Sveriges Radio • Analys: Datavetenskaplig metod • Tidsperiod: 1958-2025*
""")
if __name__ == "__main__":
app.launch()