Compare commits

..

3 Commits

Author SHA1 Message Date
3c0ad6dfac remove readme 2026-02-22 04:05:14 +01:00
8ef206ebee upload functioning version. 2026-02-22 03:25:12 +01:00
0b955ed2b9 push 2026-02-22 03:20:33 +01:00
2 changed files with 44 additions and 481 deletions

View File

@@ -1,92 +0,0 @@
# SVT Play Stream Launcher
A simple tool that lets you watch SVT Play programs directly in a video player.
## What It Does
This script makes it easy to search for and watch SVT Play programs. Instead of opening a web browser, you can:
1. Search for a program by name
2. Browse popular programs
3. Select what you want to watch
4. It opens in your video player automatically
## What You Need
Before using this script, make sure you have these programs installed:
- `yt-dlp` - Downloads video information
- `mpv` - Video player
- `zenity` - Creates the dialog windows
- `jq` - Processes data
- `curl` - Downloads information
The script will tell you if something is missing and how to install it.
## How to Use It
### Start the script
```bash
./svt.sh
```
A window will appear with two options:
- **Search for Program** - Type the name of what you want to watch
- **Browse Popular Programs** - See what's trending on SVT Play
### Example: Searching for a program
1. Click "Search for Program"
2. Type what you want (like "Wallander" or "News")
3. Wait 10-20 seconds for results (this is normal, the server is a bit slow)
4. Click on the program you want
5. It automatically opens in your video player
6. Enjoy!
### Example: Browsing popular programs
1. Click "Browse Popular Programs"
2. Wait for the list to load (10-20 seconds)
3. Scroll through and pick something
4. Press OK and it starts playing
## If Something Goes Wrong
### "Missing required tools"
You need to install something. The script will tell you exactly what to install. On Arch Linux:
```bash
sudo pacman -S yt-dlp mpv zenity jq curl
```
On Ubuntu/Debian:
```bash
sudo apt install yt-dlp mpv zenity jq curl
```
### "Program not found"
The program you searched for doesn't exist or isn't available on SVT Play right now.
### "Nothing happens after clicking OK"
This is usually just the server being slow. Wait a bit longer. The script is still working.
### "Video player doesn't open"
Make sure mpv is installed. Check with: `which mpv`
## See What's Happening
The script keeps a log of everything it's doing. If you want to see what's going on:
```bash
tail -f ~/.svtplay.log
```
This shows you all the activity in real-time. Good for understanding what the script is doing.
## Notes
- Video plays in mpv, which is a lightweight video player
- Everything is logged to `~/.svtplay.log`
- The program searches the actual SVT Play website, not a saved list
- Searching and loading takes 10-20 seconds because the server is slow - this is normal

425
svt.sh
View File

@@ -1,458 +1,113 @@
#!/bin/bash #!/bin/bash
# SVT Play Stream Launcher - Search and stream SVT Play content with zenity
# SVT Play Stream Launcher - 100% GUI Based
# Search and stream SVT Play content with zenity interface
set -euo pipefail set -euo pipefail
# Debug mode
DEBUG="${DEBUG:-0}"
WAIT_FOR_MPV=0 WAIT_FOR_MPV=0
# Logging # Parse options
LOG_FILE="${HOME}/.svtplay.log"
mkdir -p "$(dirname "$LOG_FILE")"
# Parse command line options
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--debug) DEBUG=1; shift ;;
--wait) WAIT_FOR_MPV=1; shift ;; --wait) WAIT_FOR_MPV=1; shift ;;
--log) tail -f "$LOG_FILE"; exit 0 ;;
--clear-log) rm -f "$LOG_FILE"; echo "Log cleared"; exit 0 ;;
--help|-h) --help|-h)
zenity --info --title="SVT Play Launcher" --text="SVT Play Stream Launcher\n\nA GUI-based tool to search and stream SVT Play content.\n\nUsage: ./svt.sh [OPTIONS]\n\nOptions:\n --debug Enable debug output\n --wait Wait for playback to finish\n --log View live log\n --help Show this message" zenity --info --title="SVT Play" --text="Usage: ./svt.sh [--wait|--help]"
exit 0 exit 0 ;;
;;
*) break ;; *) break ;;
esac esac
done done
# Color definitions # Force dark mode - works with GTK3 (GTK_THEME) and GTK4/libadwaita (ADW_DEBUG_COLOR_SCHEME)
readonly RED='\033[0;31m' zenity_dark() { GTK_THEME=Adwaita:dark ADW_DEBUG_COLOR_SCHEME=prefer-dark zenity "$@"; }
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m'
# Zenity dark mode helper
zenity_dark() {
GTK_THEME=Adwaita:dark zenity "$@"
}
# Logging functions
log_info() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*"
echo -e "${BLUE}[INFO]${NC} $*" >&2
echo "$msg" >> "$LOG_FILE"
}
log_success() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] [✓] $*"
echo -e "${GREEN}[✓]${NC} $*" >&2
echo "$msg" >> "$LOG_FILE"
}
log_error() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*"
echo -e "${RED}[ERROR]${NC} $*" >&2
echo "$msg" >> "$LOG_FILE"
}
log_debug() {
if [[ "$DEBUG" == "1" ]]; then
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] [DEBUG] $*"
echo -e "${BLUE}[DEBUG]${NC} $*" >&2
echo "$msg" >> "$LOG_FILE"
fi
}
# Check dependencies
check_dependencies() { check_dependencies() {
log_info "Checking dependencies..."
local required=("yt-dlp" "mpv" "zenity" "jq" "curl")
local missing=() local missing=()
for cmd in yt-dlp mpv zenity jq curl; do
for cmd in "${required[@]}"; do command -v "$cmd" &>/dev/null || missing+=("$cmd")
if ! command -v "$cmd" &>/dev/null; then
missing+=("$cmd")
fi
done done
if [[ ${#missing[@]} -gt 0 ]]; then if [[ ${#missing[@]} -gt 0 ]]; then
log_error "Missing required tools: ${missing[*]}" zenity_dark --error --title="Missing Dependencies" --text="Please install: ${missing[*]}"
zenity_dark --error --title="Missing Dependencies" --text="Please install: ${missing[*]}\n\nArch: sudo pacman -S ${missing[*]}\nDebian: sudo apt install ${missing[*]}"
exit 1 exit 1
fi fi
log_success "Dependencies OK"
} }
# Search programs via SVT Play GraphQL API
search_programs() { search_programs() {
local query="$1" local query="$1"
log_info "API: Querying searchPage with: $query" local response http_code
response=$(curl -s --connect-timeout 10 --max-time 30 -w "\n%{http_code}" -X POST \
"https://api.svt.se/contento/graphql" -H "Content-Type: application/json" \
-d "{\"query\": \"{ searchPage(query: \\\"${query}\\\", maxHits: 50) { flat { hits { teaser { heading item { urls { svtplay } } } } } } }\"}" 2>/dev/null) || return 1
# Make API call with searchPage (with longer timeout since SVT API can be slow) http_code=$(echo "$response" | tail -1)
local response
response=$(curl -s --connect-timeout 10 --max-time 30 -w "\n%{http_code}" -X POST "https://api.svt.se/contento/graphql" \
-H "Content-Type: application/json" \
-d "{\"query\": \"{ searchPage(query: \\\"${query}\\\", maxHits: 50) { flat { hits { teaser { heading } } } } }\"}" 2>/dev/null) || {
log_error "Failed to query SVT Play API (curl error)"
return 1
}
local http_code=$(echo "$response" | tail -1)
response=$(echo "$response" | sed '$d') response=$(echo "$response" | sed '$d')
log_info "API: Response HTTP $http_code" [[ "$http_code" != "200" ]] && return 1
if [[ "$http_code" != "200" ]]; then
log_error "API returned HTTP $http_code"
return 1
fi
# Check for GraphQL errors
if echo "$response" | jq -e '.errors' >/dev/null 2>&1; then
log_error "GraphQL error: $(echo "$response" | jq -r '.errors[0].message // "Unknown error"')"
return 1
fi
# Parse response - extract headings, clean HTML, and convert to slugs
local results local results
results=$(echo "$response" | jq -r '.data.searchPage.flat.hits[].teaser | select(.heading != null) | .heading as $title | ($title | ascii_downcase | gsub("<[^>]*>"; "") | gsub("[^a-z0-9 ]"; ""; "g") | gsub("^ +| +$"; "") | gsub(" +"; "-")) as $slug | $title + "|" + $slug' 2>/dev/null) results=$(echo "$response" | jq -r '
local jq_ret=$? .data.searchPage.flat.hits[].teaser |
select(.heading != null and .item.urls.svtplay != null) |
if [[ $jq_ret -ne 0 ]]; then (.heading | gsub("<[^>]*>"; "")) + "|" + (.item.urls.svtplay | ltrimstr("/"))
log_error "jq parsing failed (ret=$jq_ret)" ' 2>/dev/null)
return 1
fi
if [[ -z "$results" ]]; then
log_warn "No results from search"
return 1
fi
local result_count=$(echo "$results" | grep -c '|')
log_info "API: Search returned $result_count results"
[[ -z "$results" ]] && return 1
echo "$results" echo "$results"
} }
# Get all active programs from Tablåtjänsten
get_popular_programs() {
log_info "API: Fetching popular programs"
# Use GraphQL API with broad search query "a" to get popular/varied content (with longer timeout)
local response
response=$(curl -s --connect-timeout 10 --max-time 30 -w "\n%{http_code}" -X POST "https://api.svt.se/contento/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "{ searchPage(query: \"a\", maxHits: 50) { flat { hits { teaser { heading } } } } }"}' 2>/dev/null) || {
log_error "API: Failed to query (curl error)"
return 1
}
local http_code=$(echo "$response" | tail -1)
response=$(echo "$response" | sed '$d')
log_info "API: Popular programs response HTTP $http_code"
if [[ "$http_code" != "200" ]]; then
log_error "API returned HTTP $http_code for popular programs"
return 1
fi
# Check response is not empty
local resp_len=$(echo "$response" | wc -c)
log_info "API: Response size: $resp_len bytes"
if [[ $resp_len -lt 50 ]]; then
log_error "API: Response too small, likely error: $response"
return 1
fi
# Parse flat hits (popular programs)
# Format output: title|slug
local results
log_info "API: Parsing results with jq"
results=$(echo "$response" | jq -r '.data.searchPage.flat.hits[].teaser | select(.heading != null) | .heading as $title | ($title | ascii_downcase | gsub("<[^>]*>"; "") | gsub("[^a-z0-9 ]"; ""; "g") | gsub("^ +| +$"; "") | gsub(" +"; "-")) as $slug | $title + "|" + $slug' 2>/dev/null)
local jq_ret=$?
if [[ $jq_ret -ne 0 ]]; then
log_error "jq parsing failed (ret=$jq_ret), response: $response"
return 1
fi
if [[ -z "$results" ]]; then
log_warn "API: jq returned empty results"
log_info "API: Full response: $response"
return 1
fi
local result_count=$(echo "$results" | grep -c '|')
log_info "API: Popular programs parsed $result_count results"
echo "$results"
}
# Show search dialog
show_search_dialog() { show_search_dialog() {
log_info "Showing search dialog" zenity_dark --entry --title="Search SVT Play" --text="Program name:" --width=400 2>/dev/null
local search_term
search_term=$(zenity_dark --entry \
--title="Search SVT Play" \
--text="Enter program name to search:" \
--width=500 \
2>/dev/null)
if [[ -z "$search_term" ]]; then
log_debug "Search cancelled"
return 1
fi
log_debug "Search term: $search_term"
echo "$search_term"
} }
# Show results in zenity list
show_results_list() { show_results_list() {
local results="$1" local results="$1" temp_file="/tmp/svt_$$"
log_debug "Showing results list"
if [[ -z "$results" ]]; then # Build zenity list and mapping file
log_warn "No results provided"
zenity_dark --error --title="No Results" --text="No programs found."
return 1
fi
local result_count=$(echo "$results" | wc -l)
log_debug "Found $result_count results"
# Create temp file with formatted list for zenity
local temp_file="/tmp/svt_search_$$.txt"
local idx=0 local idx=0
while IFS='|' read -r title slug; do while IFS='|' read -r title slug; do
[[ -z "$title" || -z "$slug" ]] && continue [[ -z "$title" || -z "$slug" ]] && continue
((idx++)) ((idx++))
title=$(echo "$title" | sed 's/&amp;/\&/g; s/&quot;/"/g; s/&lt;/</g; s/&gt;/>/g')
# Clean HTML tags
title=$(echo "$title" | sed 's/<[^>]*>//g' | sed 's/&amp;/\&/g' | sed 's/&quot;/"/g' | sed "s/&apos;/'/g" | sed 's/&lt;/</g' | sed 's/&gt;/>/g')
# Save title->slug mapping
echo "$title=$slug" >> "$temp_file.map" echo "$title=$slug" >> "$temp_file.map"
[[ $idx -eq 1 ]] && echo "TRUE" >> "$temp_file" || echo "FALSE" >> "$temp_file"
if [[ $idx -eq 1 ]]; then
echo "TRUE" >> "$temp_file"
else
echo "FALSE" >> "$temp_file"
fi
echo "$title" >> "$temp_file" echo "$title" >> "$temp_file"
done <<< "$results" done <<< "$results"
[[ $idx -eq 0 ]] && { rm -f "$temp_file"*; return 1; } [[ $idx -eq 0 ]] && { rm -f "$temp_file"*; return 1; }
log_debug "Built list with $idx items"
# Display zenity dialog
local selected local selected
selected=$(zenity_dark --list --title="Search Results" --text="Select a program:" --column="Select" --column="Program" --radiolist --width=700 --height=500 < "$temp_file" 2>/dev/null) selected=$(zenity_dark --list --title="Results" --text="Select program:" \
--column="Select" --column="Program" --radiolist --width=700 --height=500 < "$temp_file" 2>/dev/null)
if [[ -z "$selected" ]]; then
log_debug "User cancelled"
rm -f "$temp_file"*
return 1
fi
# Look up slug
local slug
slug=$(grep "^$selected=" "$temp_file.map" 2>/dev/null | cut -d= -f2)
local slug=""
[[ -n "$selected" ]] && slug=$(grep "^${selected}=" "$temp_file.map" 2>/dev/null | cut -d= -f2)
rm -f "$temp_file"* rm -f "$temp_file"*
if [[ -z "$slug" ]]; then [[ -z "$slug" ]] && return 1
log_error "Could not map selection to slug"
return 1
fi
log_success "Selected: $selected"
echo "$slug" echo "$slug"
} }
# Show popular programs
show_popular_programs() {
log_info "Fetching popular programs from SVT Play..."
# Get programs (API call takes 10-20 seconds)
local programs
programs=$(get_popular_programs)
if [[ -z "$programs" ]]; then
log_error "No popular programs fetched"
zenity_dark --error --title="Error" --text="Failed to fetch popular programs from SVT Play."
return 1
fi
log_info "Got $(echo "$programs" | wc -l) results, preparing list..."
# Create temp file with formatted list for zenity
local temp_file="/tmp/svt_list_$$.txt"
local idx=0
local first_title=""
while IFS='|' read -r title slug; do
[[ -z "$title" || -z "$slug" ]] && continue
((idx++))
# Clean HTML tags
title=$(echo "$title" | sed 's/<[^>]*>//g' | sed 's/&amp;/\&/g' | sed 's/&quot;/"/g' | sed "s/&apos;/'/g" | sed 's/&lt;/</g' | sed 's/&gt;/>/g')
# Save title->slug mapping
echo "$title=$slug" >> "$temp_file.map"
if [[ $idx -eq 1 ]]; then
echo "TRUE" >> "$temp_file"
first_title="$title"
else
echo "FALSE" >> "$temp_file"
fi
echo "$title" >> "$temp_file"
done <<< "$programs"
[[ $idx -eq 0 ]] && { log_error "No valid items"; rm -f "$temp_file"*; return 1; }
log_info "Built list with $idx items"
# Display zenity dialog with the list
log_info "Showing zenity list dialog..."
local selected
selected=$(zenity_dark --list --title="Popular Programs" --text="Select a program to stream:" --column="Select" --column="Program" --radiolist --width=700 --height=600 < "$temp_file" 2>/dev/null)
local ret=$?
if [[ $ret -ne 0 ]] || [[ -z "$selected" ]]; then
log_debug "User cancelled or dialog failed (ret=$ret)"
rm -f "$temp_file"*
return 1
fi
# Look up slug from the selected title
local slug
slug=$(grep "^$selected=" "$temp_file.map" 2>/dev/null | cut -d= -f2)
rm -f "$temp_file"*
if [[ -z "$slug" ]]; then
log_error "Could not find slug for: $selected"
return 1
fi
log_success "Selected: $selected"
echo "$slug"
}
# Show main menu
show_main_menu() {
log_info "Showing main menu"
local choice
choice=$(zenity_dark --list \
--title="SVT Play Stream Launcher" \
--text="Select an option:" \
--column="Option" \
"Search for Program" \
"Browse Popular Programs" \
--width=400 \
--height=200 \
2>/dev/null)
if [[ -z "$choice" ]]; then
log_debug "User cancelled main menu"
return 1
fi
echo "$choice"
}
# Play program directly
play_program() { play_program() {
local slug="$1" local url="https://www.svtplay.se/$1"
local url="https://www.svtplay.se/$slug"
log_info "Playing program: $slug"
log_debug "URL: $url"
# Verify URL is accessible
if ! yt-dlp --quiet --dump-json "$url" &>/dev/null; then
log_error "Failed to access program: $slug"
zenity_dark --error --title="Error" --text="Failed to access program. It may not be available."
return 1
fi
log_success "Starting playback for: $slug"
# Launch mpv with yt-dlp integration
mpv --force-window=immediate "$url" & mpv --force-window=immediate "$url" &
local pid=$! [[ "$WAIT_FOR_MPV" == "1" ]] && wait $! 2>/dev/null
log_success "Playback started (PID: $pid)"
if [[ "$WAIT_FOR_MPV" == "1" ]]; then
log_info "Waiting for mpv to finish..."
wait $pid 2>/dev/null
log_success "Playback finished"
else
log_info "Playback continues in background"
fi
} }
# Main function
main() { main() {
log_info "═════ SVT Play Stream Launcher started ═════"
log_info "PID: $$, DIR: $PWD"
check_dependencies check_dependencies
# Show main menu
local menu_choice
menu_choice=$(show_main_menu) || exit 0
local program_slug
case "$menu_choice" in
"Search for Program")
local search_term local search_term
search_term=$(show_search_dialog) || exit 0 search_term=$(show_search_dialog) || exit 0
[[ -z "$search_term" ]] && exit 0
log_info "Searching for: $search_term" local results
results=$(search_programs "$search_term") || {
# Get search results (API call takes 10-20 seconds)
search_results=$(search_programs "$search_term")
if [[ -z "$search_results" ]]; then
zenity_dark --error --title="No Results" --text="No programs found for: $search_term" zenity_dark --error --title="No Results" --text="No programs found for: $search_term"
exit 0 exit 0
fi }
program_slug=$(show_results_list "$search_results") || exit 0 local slug
;; slug=$(show_results_list "$results") || exit 0
"Browse Popular Programs")
program_slug=$(show_popular_programs) || exit 0
;;
*)
log_error "Invalid menu choice: $menu_choice"
exit 1
;;
esac
# Play the selected program play_program "$slug"
play_program "$program_slug"
log_success "Session complete"
log_info "═════════════════════════════════════════════════════"
} }
# Only run main if script is executed directly (not sourced)
[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@" [[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@"