Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc0c573c51 | |||
| 28bd9ad504 | |||
| 657b194dc7 | |||
| 27ffd35826 | |||
| da64cb2b6f | |||
| 09e96a55d4 | |||
| e4027402ed | |||
| 4dc1599633 | |||
| 71a8a19491 | |||
| 1ec4333856 | |||
| f98fe4635c |
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "vrobbler"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
description = ""
|
||||
authors = ["Colin Powell <colin@unbl.ink>"]
|
||||
|
||||
|
||||
8
vrobbler/apps/music/context_processors.py
Normal file
8
vrobbler/apps/music/context_processors.py
Normal file
@ -0,0 +1,8 @@
|
||||
from music.models import Artist, Album
|
||||
|
||||
|
||||
def music_lists(request):
|
||||
return {
|
||||
"artist_list": Artist.objects.all(),
|
||||
"album_list": Album.objects.all(),
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
# Generated by Django 4.1.5 on 2023-01-08 21:31
|
||||
|
||||
from django.db import migrations, models
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('music', '0002_alter_album_year'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='album',
|
||||
name='uuid',
|
||||
field=models.UUIDField(
|
||||
blank=True, default=uuid.uuid4, editable=False, null=True
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='artist',
|
||||
name='uuid',
|
||||
field=models.UUIDField(
|
||||
blank=True, default=uuid.uuid4, editable=False, null=True
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='track',
|
||||
name='uuid',
|
||||
field=models.UUIDField(
|
||||
blank=True, default=uuid.uuid4, editable=False, null=True
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -1,15 +1,19 @@
|
||||
import logging
|
||||
from typing import Dict, Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from django.apps.config import cached_property
|
||||
|
||||
from django.db import models
|
||||
from django_extensions.db.models import TimeStampedModel
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from vrobbler.apps.music.constants import JELLYFIN_POST_KEYS as KEYS
|
||||
from django_extensions.db.models import TimeStampedModel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
BNULL = {"blank": True, "null": True}
|
||||
|
||||
|
||||
class Album(TimeStampedModel):
|
||||
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
|
||||
name = models.CharField(max_length=255)
|
||||
year = models.IntegerField(**BNULL)
|
||||
musicbrainz_id = models.CharField(max_length=255, **BNULL)
|
||||
@ -19,16 +23,26 @@ class Album(TimeStampedModel):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def mb_link(self):
|
||||
return f"https://musicbrainz.org/release/{self.musicbrainz_id}"
|
||||
|
||||
|
||||
class Artist(TimeStampedModel):
|
||||
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
|
||||
name = models.CharField(max_length=255)
|
||||
musicbrainz_id = models.CharField(max_length=255, **BNULL)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def mb_link(self):
|
||||
return f"https://musicbrainz.org/artist/{self.musicbrainz_id}"
|
||||
|
||||
|
||||
class Track(TimeStampedModel):
|
||||
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
|
||||
title = models.CharField(max_length=255, **BNULL)
|
||||
artist = models.ForeignKey(Artist, on_delete=models.DO_NOTHING)
|
||||
album = models.ForeignKey(Album, on_delete=models.DO_NOTHING, **BNULL)
|
||||
@ -39,6 +53,14 @@ class Track(TimeStampedModel):
|
||||
def __str__(self):
|
||||
return f"{self.title} by {self.artist}"
|
||||
|
||||
@property
|
||||
def mb_link(self):
|
||||
return f"https://musicbrainz.org/recording/{self.musicbrainz_id}"
|
||||
|
||||
@cached_property
|
||||
def scrobble_count(self):
|
||||
return self.scrobble_set.filter(in_progress=False).count()
|
||||
|
||||
@classmethod
|
||||
def find_or_create(
|
||||
cls, artist_dict: Dict, album_dict: Dict, track_dict: Dict
|
||||
@ -74,6 +96,6 @@ class Track(TimeStampedModel):
|
||||
if created:
|
||||
logger.debug(f"Created new track: {track}")
|
||||
else:
|
||||
logger.debug(f"Found track{track}")
|
||||
logger.debug(f"Found track {track}")
|
||||
|
||||
return track
|
||||
|
||||
@ -13,7 +13,7 @@ class ScrobbleAdmin(admin.ModelAdmin):
|
||||
"playback_position",
|
||||
"in_progress",
|
||||
)
|
||||
list_filter = ("video",)
|
||||
list_filter = ("in_progress", "source")
|
||||
ordering = ("-timestamp",)
|
||||
|
||||
|
||||
|
||||
@ -42,6 +42,12 @@ class Scrobble(TimeStampedModel):
|
||||
(self.playback_position_ticks / self.media_run_time_ticks)
|
||||
* 100
|
||||
)
|
||||
# If we don't have media_run_time_ticks, let's guess from created time
|
||||
now = timezone.now()
|
||||
playback_duration = (now - self.created).seconds
|
||||
if playback_duration and self.track.run_time:
|
||||
return int((playback_duration / int(self.track.run_time)) * 100)
|
||||
|
||||
return 0
|
||||
|
||||
@property
|
||||
@ -97,6 +103,15 @@ class Scrobble(TimeStampedModel):
|
||||
.order_by('-modified')
|
||||
.first()
|
||||
)
|
||||
# Check if playback_position_ticks has changed from this scrobble
|
||||
scrobble_changed = (
|
||||
scrobble
|
||||
and scrobble.playback_position_ticks
|
||||
!= jellyfin_data['playback_position_ticks']
|
||||
)
|
||||
if not scrobble_changed:
|
||||
logger.info('Scrobble playback has not changed, not scrobbling')
|
||||
return
|
||||
|
||||
# Backoff is how long until we consider this a new scrobble
|
||||
backoff = timezone.now() + timedelta(minutes=VIDEO_BACKOFF)
|
||||
|
||||
7
vrobbler/apps/scrobbles/utils.py
Normal file
7
vrobbler/apps/scrobbles/utils.py
Normal file
@ -0,0 +1,7 @@
|
||||
def convert_to_seconds(run_time: str) -> int:
|
||||
"""Jellyfin sends run time as 00:00:00 string. We want the run time to
|
||||
actually be in seconds so we'll convert it"""
|
||||
if ":" in run_time:
|
||||
run_time_list = run_time.split(":")
|
||||
run_time = (int(run_time_list[1]) * 60) + int(run_time_list[2])
|
||||
return int(run_time)
|
||||
@ -8,6 +8,7 @@ from django.db.models.fields import timezone
|
||||
from django.utils import timezone
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic.list import ListView
|
||||
from music.constants import JELLYFIN_POST_KEYS as KEYS
|
||||
from music.models import Track
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import api_view
|
||||
@ -18,10 +19,9 @@ from scrobbles.constants import (
|
||||
)
|
||||
from scrobbles.models import Scrobble
|
||||
from scrobbles.serializers import ScrobbleSerializer
|
||||
from scrobbles.utils import convert_to_seconds
|
||||
from videos.models import Video
|
||||
|
||||
from vrobbler.apps.music.constants import JELLYFIN_POST_KEYS as KEYS
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TRUTHY_VALUES = [
|
||||
@ -103,11 +103,15 @@ def jellyfin_websocket(request):
|
||||
"musicbrainz_albumartist_id": data_dict.get(KEYS["ARTIST_MB_ID"]),
|
||||
}
|
||||
|
||||
# Convert ticks from Jellyfin from microseconds to nanoseconds
|
||||
# Ain't nobody got time for nanoseconds
|
||||
track_dict = {
|
||||
"title": data_dict.get("Name", ""),
|
||||
"musicbrainz_id": data_dict.get(KEYS["TRACK_MB_ID"], None),
|
||||
"run_time_ticks": data_dict.get(KEYS["RUN_TIME_TICKS"], None),
|
||||
"run_time": data_dict.get(KEYS["RUN_TIME"], None),
|
||||
"run_time_ticks": data_dict.get(KEYS["RUN_TIME_TICKS"], None)
|
||||
// 10000,
|
||||
"run_time": convert_to_seconds(
|
||||
data_dict.get(KEYS["RUN_TIME"], None)
|
||||
),
|
||||
}
|
||||
track = Track.find_or_create(artist_dict, album_dict, track_dict)
|
||||
|
||||
@ -123,8 +127,11 @@ def jellyfin_websocket(request):
|
||||
jellyfin_data = {
|
||||
"user_id": request.user.id,
|
||||
"timestamp": parse(data_dict.get("UtcTimestamp")),
|
||||
"playback_position_ticks": data_dict.get("PlaybackPositionTicks"),
|
||||
"playback_position": data_dict.get("PlaybackPosition"),
|
||||
"playback_position_ticks": data_dict.get("PlaybackPositionTicks")
|
||||
// 10000,
|
||||
"playback_position": convert_to_seconds(
|
||||
data_dict.get("PlaybackPosition")
|
||||
),
|
||||
"source": "Jellyfin",
|
||||
"source_id": data_dict.get('MediaSourceId'),
|
||||
"is_paused": data_dict.get("IsPaused") in TRUTHY_VALUES,
|
||||
@ -136,6 +143,10 @@ def jellyfin_websocket(request):
|
||||
video, request.user.id, jellyfin_data
|
||||
)
|
||||
if track:
|
||||
# Prefer Mopidy MD IDs to Jellyfin, so skip if we already have one
|
||||
if not track.musicbrainz_id:
|
||||
track.musicbrainz_id = data_dict.get(KEYS["TRACK_MB_ID"], None)
|
||||
track.save()
|
||||
scrobble = Scrobble.create_or_update_for_track(
|
||||
track, request.user.id, jellyfin_data
|
||||
)
|
||||
@ -169,7 +180,6 @@ def mopidy_websocket(request):
|
||||
|
||||
track_dict = {
|
||||
"title": data_dict.get("name"),
|
||||
"musicbrainz_id": data_dict.get("musicbrainz_track_id"),
|
||||
"run_time_ticks": data_dict.get("run_time_ticks"),
|
||||
"run_time": data_dict.get("run_time"),
|
||||
}
|
||||
@ -186,6 +196,9 @@ def mopidy_websocket(request):
|
||||
|
||||
scrobble = None
|
||||
if track:
|
||||
# Jellyfin MB ids suck, so always overwrite with Mopidy if they're offering
|
||||
track.musicbrainz_id = data_dict.get("musicbrainz_track_id")
|
||||
track.save()
|
||||
scrobble = Scrobble.create_or_update_for_track(
|
||||
track, request.user.id, mopidy_data
|
||||
)
|
||||
|
||||
8
vrobbler/apps/videos/context_processors.py
Normal file
8
vrobbler/apps/videos/context_processors.py
Normal file
@ -0,0 +1,8 @@
|
||||
from videos.models import Video, Series
|
||||
|
||||
|
||||
def video_lists(request):
|
||||
return {
|
||||
"movie_list": Video.objects.filter(video_type=Video.VideoType.MOVIE),
|
||||
"series_list": Series.objects.all(),
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
# Generated by Django 4.1.5 on 2023-01-08 21:31
|
||||
|
||||
from django.db import migrations, models
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('videos', '0003_alter_video_run_time_ticks'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='series',
|
||||
name='uuid',
|
||||
field=models.UUIDField(
|
||||
blank=True, default=uuid.uuid4, editable=False, null=True
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='video',
|
||||
name='uuid',
|
||||
field=models.UUIDField(
|
||||
blank=True, default=uuid.uuid4, editable=False, null=True
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -1,21 +1,31 @@
|
||||
import logging
|
||||
from typing import Dict, Tuple
|
||||
from typing import Dict
|
||||
from uuid import uuid4
|
||||
|
||||
from django.db import models
|
||||
from django_extensions.db.models import TimeStampedModel
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_extensions.db.models import TimeStampedModel
|
||||
from scrobbles.utils import convert_to_seconds
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
BNULL = {"blank": True, "null": True}
|
||||
|
||||
|
||||
class Series(TimeStampedModel):
|
||||
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
|
||||
name = models.CharField(max_length=255)
|
||||
overview = models.TextField(**BNULL)
|
||||
tagline = models.TextField(**BNULL)
|
||||
# tvdb_id = models.CharField(max_length=20, **BNULL)
|
||||
# imdb_id = models.CharField(max_length=20, **BNULL)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def imdb_link(self):
|
||||
return f"https://www.imdb.com/title/{self.imdb_id}"
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = "series"
|
||||
|
||||
@ -27,12 +37,13 @@ class Video(TimeStampedModel):
|
||||
MOVIE = 'M', _('Movie')
|
||||
|
||||
# General fields
|
||||
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
|
||||
title = models.CharField(max_length=255, **BNULL)
|
||||
video_type = models.CharField(
|
||||
max_length=1,
|
||||
choices=VideoType.choices,
|
||||
default=VideoType.UNKNOWN,
|
||||
)
|
||||
title = models.CharField(max_length=255, **BNULL)
|
||||
overview = models.TextField(**BNULL)
|
||||
tagline = models.TextField(**BNULL)
|
||||
run_time = models.CharField(max_length=8, **BNULL)
|
||||
@ -54,6 +65,12 @@ class Video(TimeStampedModel):
|
||||
return f"{self.tv_series} - Season {self.season_number}, Episode {self.episode_number}"
|
||||
return self.title
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("videos:video_detail", kwargs={'slug': self.uuid})
|
||||
|
||||
@property
|
||||
def imdb_link(self):
|
||||
return f"https://www.imdb.com/title/{self.imdb_id}"
|
||||
|
||||
@classmethod
|
||||
def find_or_create(cls, data_dict: Dict) -> "Video":
|
||||
@ -69,13 +86,15 @@ class Video(TimeStampedModel):
|
||||
"year": data_dict.get("Year", ""),
|
||||
"overview": data_dict.get("Overview", None),
|
||||
"tagline": data_dict.get("Tagline", None),
|
||||
"run_time_ticks": data_dict.get("RunTimeTicks", None),
|
||||
"run_time": data_dict.get("RunTime", None),
|
||||
"run_time_ticks": data_dict.get("RunTimeTicks", 0) // 10000,
|
||||
"run_time": convert_to_seconds(data_dict.get("RunTime", "")),
|
||||
}
|
||||
|
||||
if data_dict.get("ItemType", "") == "Episode":
|
||||
series_name = data_dict.get("SeriesName", "")
|
||||
series, series_created = Series.objects.get_or_create(name=series_name)
|
||||
series, series_created = Series.objects.get_or_create(
|
||||
name=series_name
|
||||
)
|
||||
if series_created:
|
||||
logger.debug(f"Created new series {series}")
|
||||
else:
|
||||
@ -87,7 +106,6 @@ class Video(TimeStampedModel):
|
||||
video_dict["episode_number"] = data_dict.get("EpisodeNumber", "")
|
||||
video_dict["season_number"] = data_dict.get("SeasonNumber", "")
|
||||
|
||||
|
||||
video, created = cls.objects.get_or_create(**video_dict)
|
||||
if created:
|
||||
logger.debug(f"Created new video: {video}")
|
||||
|
||||
21
vrobbler/apps/videos/urls.py
Normal file
21
vrobbler/apps/videos/urls.py
Normal file
@ -0,0 +1,21 @@
|
||||
from django.urls import path
|
||||
from videos import views
|
||||
|
||||
app_name = 'scrobbles'
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
# path('', views.scrobble_endpoint, name='scrobble-list'),
|
||||
path("movies/", views.MovieListView.as_view(), name='movie_list'),
|
||||
path('series/', views.SeriesListView.as_view(), name='series_list'),
|
||||
path(
|
||||
'series/<slug:slug>/',
|
||||
views.SeriesDetailView.as_view(),
|
||||
name='series_detail',
|
||||
),
|
||||
path(
|
||||
'video/<slug:slug>/',
|
||||
views.VideoDetailView.as_view(),
|
||||
name='video_detail',
|
||||
),
|
||||
]
|
||||
25
vrobbler/apps/videos/views.py
Normal file
25
vrobbler/apps/videos/views.py
Normal file
@ -0,0 +1,25 @@
|
||||
from django.views import generic
|
||||
from videos.models import Series, Video
|
||||
|
||||
# class VideoIndexView():
|
||||
|
||||
|
||||
class MovieListView(generic.ListView):
|
||||
model = Video
|
||||
|
||||
def get_queryset(self):
|
||||
return Video.objects.filter(video_type=Video.VideoType.MOVIE)
|
||||
|
||||
|
||||
class SeriesListView(generic.ListView):
|
||||
model = Series
|
||||
|
||||
|
||||
class SeriesDetailView(generic.DetailView):
|
||||
model = Series
|
||||
slug_field = 'uuid'
|
||||
|
||||
|
||||
class VideoDetailView(generic.DetailView):
|
||||
model = Video
|
||||
slug_field = 'uuid'
|
||||
@ -79,6 +79,7 @@ INSTALLED_APPS = [
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"django.contrib.sites",
|
||||
"django.contrib.humanize",
|
||||
"django_filters",
|
||||
"django_extensions",
|
||||
'rest_framework.authtoken',
|
||||
@ -118,6 +119,8 @@ TEMPLATES = [
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"videos.context_processors.video_lists",
|
||||
"music.context_processors.music_lists",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<!doctype html>
|
||||
<html class="no-js" lang="">
|
||||
<head>
|
||||
<title>{% block page_title %}Welcome{% endblock %} | Vrobbler » For video scrobbling</title>
|
||||
<title>{% block page_title %}Scrobble all the things{% endblock %} @ Vrobbler</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<meta name="description" content="">
|
||||
@ -75,7 +75,7 @@
|
||||
<![endif]-->
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<a class="navbar-brand" href="#">Vrobbler</a>
|
||||
<a class="navbar-brand" href="/">Vrobbler</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
@ -88,9 +88,9 @@
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Movies</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<a class="dropdown-item" href="{ url "games:gamesystem_list" %}">All</a>
|
||||
{% for system in game_systems %}
|
||||
<a class="dropdown-item" href="{{system.get_absolute_url}}">{{system.name}} ({{system.game_set.count}})</a>
|
||||
<a class="dropdown-item" href="{% url "videos:movie_list" %}">All</a>
|
||||
{% for movie in movie_list %}
|
||||
<a class="dropdown-item" href="{{movie.get_absolute_url}}">{{movie.title}}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</li>
|
||||
@ -98,8 +98,8 @@
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Shows</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<a class="dropdown-item" href="{ url "games:gamecollection_list" %}">All</a>
|
||||
{% for collection in game_collections %}
|
||||
<a class="dropdown-item" href="{{collection.get_absolute_url}}">{{collection.name}} ({{collection.games.count}})</a>
|
||||
{% for series in series_list %}
|
||||
<a class="dropdown-item" href="{{series.get_absolute_url}}">{{series.name}}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load humanize %}
|
||||
|
||||
{% block title %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@ -10,8 +12,8 @@
|
||||
<dl class="latest-scrobble">
|
||||
<dt>{{scrobble.video.title}} - {{scrobble.video}}</dt>
|
||||
<dd>
|
||||
{{scrobble.timestamp|date:"D, M j Y"}} |
|
||||
<a href="https://www.imdb.com/title/{{scrobble.video.imdb_id}}">IMDB</a>
|
||||
Started {{scrobble.created|naturaltime}} from {{scrobble.source}}
|
||||
|
||||
<div class="progress-bar">
|
||||
<span class="progress-bar-fill" style="width: {{scrobble.percent_played}}%;"></span>
|
||||
</div>
|
||||
@ -22,8 +24,7 @@
|
||||
<dl class="latest-scrobble">
|
||||
<dt>{{scrobble.track.title}} by {{scrobble.track.artist}} from {{scrobble.track.album}}</dt>
|
||||
<dd>
|
||||
{{scrobble.timestamp|date:"D, M j Y"}} |
|
||||
<a href="https://www.imdb.com/title/{{scrobble.track.musicbrainz_id}}">MusicBrainz</a>
|
||||
Started {{scrobble.created|naturaltime}} from {{scrobble.source}}
|
||||
<div class="progress-bar">
|
||||
<span class="progress-bar-fill" style="width: {{scrobble.percent_played}}%;"></span>
|
||||
</div>
|
||||
@ -37,12 +38,12 @@
|
||||
<ul>
|
||||
{% for scrobble in object_list %}
|
||||
<li>
|
||||
{{scrobble.timestamp|date:"D, M j Y"}}:
|
||||
{{scrobble.timestamp|naturaltime}}:
|
||||
{% if scrobble.video %}
|
||||
📼 <a href="https://www.imdb.com/title/{{scrobble.video.imdb_id}}">{{scrobble.video}}{% if scrobble.video.video_type == 'E' %} - {{scrobble.video.title}}{% endif %}</a></li>
|
||||
🎥 watched <a href="{{scrobble.video.imdb_link}}">{{scrobble.video}}{% if scrobble.video.video_type == 'E' %} - {{scrobble.video.title}}{% endif %}</a></li>
|
||||
{% endif %}
|
||||
{% if scrobble.track %}
|
||||
🎶 <a href="https://musicbrainz.org/recording/{{scrobble.track.album.musicbrainz_id}}">{{scrobble.track}} by {{scrobble.track.artist}}</a></li>
|
||||
🎶 listened to <a href="{{scrobble.track.mb_link}}">{{scrobble.track.title}}</a> by <a href="{{scrobble.track.artist.mb_link}}">{{scrobble.track.artist}}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
12
vrobbler/templates/videos/movie_list.html
Normal file
12
vrobbler/templates/videos/movie_list.html
Normal file
@ -0,0 +1,12 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2>Movies</h2>
|
||||
|
||||
<ul>
|
||||
{% for movie in object_list %}
|
||||
<li>{{movie}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
14
vrobbler/templates/videos/video_detail.html
Normal file
14
vrobbler/templates/videos/video_detail.html
Normal file
@ -0,0 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Videos{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{object}}
|
||||
|
||||
{% for scrobble in object.scrobble_set.all %}
|
||||
<ul>
|
||||
<li>{{scrobble}}</li>
|
||||
</ul>
|
||||
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
13
vrobbler/templates/videos/video_list.html
Normal file
13
vrobbler/templates/videos/video_list.html
Normal file
@ -0,0 +1,13 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Movies{% endblock %}
|
||||
{% block content %}
|
||||
{% for movie in object_list %}
|
||||
<dl>
|
||||
<dt>{{movie}}</dt>
|
||||
{% for scrobble in movie.scrobble_set.all %}
|
||||
<dd>{{scrobble}}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
@ -2,20 +2,11 @@ from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
from scrobbles.views import RecentScrobbleList
|
||||
from scrobbles import urls as scrobble_urls
|
||||
|
||||
# from scrobbles.api.views import ScrobbleViewSet
|
||||
# from media_types.api.views import (
|
||||
# ShowViewSet,
|
||||
# MovieViewSet,
|
||||
# )
|
||||
from rest_framework import routers
|
||||
from scrobbles.views import RecentScrobbleList
|
||||
from videos import urls as video_urls
|
||||
|
||||
# router = routers.DefaultRouter()
|
||||
# router.register(r"scrobbles", ScrobbleViewSet)
|
||||
# router.register(r"shows", ShowViewSet)
|
||||
# router.register(r"movies", MovieViewSet)
|
||||
from scrobbles import urls as scrobble_urls
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
@ -24,6 +15,7 @@ urlpatterns = [
|
||||
# path("movies/", include(movies, namespace="movies")),
|
||||
# path("shows/", include(shows, namespace="shows")),
|
||||
path("api/v1/scrobbles/", include(scrobble_urls, namespace="scrobbles")),
|
||||
path("", include(video_urls, namespace="videos")),
|
||||
path("", RecentScrobbleList.as_view(), name="home"),
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user