2018-10-12 23:00:20 +02:00
|
|
|
import json
|
|
|
|
import os
|
2018-10-14 20:52:05 +02:00
|
|
|
from shutil import copytree, copy
|
2018-10-12 23:00:20 +02:00
|
|
|
from pathlib import Path, PurePath
|
|
|
|
import subprocess
|
|
|
|
|
|
|
|
from jinja2 import Template, Environment, PackageLoader, select_autoescape
|
2019-12-07 12:24:28 +01:00
|
|
|
from jinja2.utils import Markup
|
2018-10-14 20:52:05 +02:00
|
|
|
import asstosrt
|
|
|
|
import srt
|
2018-10-12 23:00:20 +02:00
|
|
|
import webvtt
|
|
|
|
|
|
|
|
|
2018-10-14 20:52:05 +02:00
|
|
|
class SongPage:
|
2018-10-12 23:00:20 +02:00
|
|
|
THUMBNAIL_GEOMETRY = '200x200'
|
|
|
|
|
2019-09-12 20:02:58 +02:00
|
|
|
def __init__(self, path, root):
|
2018-10-12 23:00:20 +02:00
|
|
|
self.name = path.name
|
|
|
|
self.original = None
|
|
|
|
self.author = None
|
|
|
|
self.path = path
|
2019-09-12 20:02:58 +02:00
|
|
|
self.root = root
|
2018-10-12 23:00:20 +02:00
|
|
|
self.video = None
|
2018-10-14 20:52:05 +02:00
|
|
|
self.video_type = None
|
2018-10-12 23:00:20 +02:00
|
|
|
self.vtt = None
|
|
|
|
self.srt = None
|
|
|
|
self.ass = None
|
|
|
|
self.cover = None
|
|
|
|
self.thumbnail = None
|
|
|
|
self.files = []
|
|
|
|
self.search_media()
|
|
|
|
|
|
|
|
def search_media(self):
|
|
|
|
for entry in self.path.iterdir():
|
|
|
|
if entry.name == 'metadata.json':
|
|
|
|
with entry.open('r') as metadatafile:
|
|
|
|
self.metadata = json.load(metadatafile)
|
|
|
|
if 'name' in self.metadata:
|
|
|
|
self.name = self.metadata['name']
|
|
|
|
if 'original' in self.metadata:
|
|
|
|
self.original = self.metadata['original']
|
|
|
|
if 'author' in self.metadata:
|
|
|
|
self.author = self.metadata['author']
|
|
|
|
elif entry.name.endswith('mp4'):
|
|
|
|
self.video = entry
|
2018-10-14 20:52:05 +02:00
|
|
|
self.video_type = 'video/mp4'
|
|
|
|
self.files.append(entry)
|
|
|
|
elif entry.name.endswith('webm'):
|
|
|
|
self.video = entry
|
|
|
|
self.video_type = 'video/webm'
|
|
|
|
self.files.append(entry)
|
|
|
|
elif entry.name.endswith('ogv'):
|
|
|
|
self.video = entry
|
|
|
|
self.video_type = 'video/ogg'
|
2018-10-12 23:00:20 +02:00
|
|
|
self.files.append(entry)
|
|
|
|
elif entry.name.endswith('vtt'):
|
|
|
|
self.vtt = entry
|
2019-09-03 12:31:24 +02:00
|
|
|
elif entry.name == "{}.srt".format(self.path.name):
|
2018-10-12 23:00:20 +02:00
|
|
|
self.srt = entry
|
|
|
|
self.files.append(entry)
|
|
|
|
elif entry.name.endswith('ass'):
|
|
|
|
self.ass = entry
|
|
|
|
self.files.append(entry)
|
|
|
|
elif entry.name.endswith('_thumbnail.jpg'):
|
|
|
|
self.thumbnail = entry
|
|
|
|
elif entry.name.endswith('jpg'):
|
|
|
|
self.cover = entry
|
|
|
|
elif entry.name == 'index.html':
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
self.files.append(entry)
|
|
|
|
|
2018-10-14 20:52:05 +02:00
|
|
|
if self.srt is None and self.ass is not None:
|
|
|
|
self.srt = self.path / "{}.srt".format(self.path.name)
|
|
|
|
with self.ass.open('r') as assfile, self.srt.open('w') as srtfile:
|
|
|
|
srtfile.write(asstosrt.convert(assfile))
|
|
|
|
self.files.append(self.srt)
|
|
|
|
|
2018-10-12 23:00:20 +02:00
|
|
|
if self.vtt is None and self.srt is not None:
|
|
|
|
srtfile = self.path / self.srt
|
|
|
|
self.vtt = self.path / "{}.vtt".format(self.path.name)
|
|
|
|
webvtt.from_srt(str(self.srt.absolute())).save(str(self.vtt.absolute()))
|
|
|
|
|
|
|
|
if self.cover is None and self.video is not None:
|
2018-10-14 20:52:05 +02:00
|
|
|
self.cover = self.path / "{}.jpg".format(self.path.name)
|
2018-10-12 23:00:20 +02:00
|
|
|
command = [
|
|
|
|
'ffmpeg',
|
|
|
|
'-loglevel', 'quiet',
|
|
|
|
'-i', str(self.video.absolute()),
|
|
|
|
'-vcodec', 'mjpeg',
|
|
|
|
'-vframes', '1',
|
|
|
|
'-an',
|
|
|
|
'-f', 'rawvideo',
|
|
|
|
'-ss', '2',
|
|
|
|
str(self.cover.absolute()),
|
|
|
|
]
|
|
|
|
subprocess.check_call(command)
|
|
|
|
|
|
|
|
if self.thumbnail is None and self.cover is not None:
|
2018-10-14 20:52:05 +02:00
|
|
|
self.thumbnail = self.path / "{}_thumbnail.jpg".format(self.path.name)
|
2018-10-12 23:00:20 +02:00
|
|
|
subprocess.check_call([
|
|
|
|
'convert',
|
|
|
|
str(self.cover.absolute()),
|
|
|
|
'-resize', self.THUMBNAIL_GEOMETRY,
|
|
|
|
str(self.thumbnail.absolute()),
|
|
|
|
])
|
|
|
|
|
2019-12-07 12:24:28 +01:00
|
|
|
@property
|
|
|
|
def publish(self):
|
|
|
|
has_subtitles = self.ass or self.srt or self.vtt
|
|
|
|
has_video = self.video
|
|
|
|
return has_video and has_subtitles
|
|
|
|
|
2018-10-12 23:00:20 +02:00
|
|
|
def get_context_data(self):
|
2018-10-14 20:52:05 +02:00
|
|
|
parsed_srt = None
|
|
|
|
if self.srt:
|
|
|
|
with self.srt.open('r') as srtfile:
|
|
|
|
try:
|
|
|
|
srt_str = srtfile.read().encode('utf-8').decode('utf-8-sig')
|
|
|
|
parsed_srt = list(srt.parse(srt_str))
|
|
|
|
except Exception as e:
|
|
|
|
print("{}: srt parse error: {}".format(self.path.name, e))
|
2019-09-12 20:02:58 +02:00
|
|
|
root_path = os.path.relpath(self.root, self.path)
|
2018-10-12 23:00:20 +02:00
|
|
|
return {
|
2018-10-14 20:52:05 +02:00
|
|
|
'song': self,
|
|
|
|
'parsed_srt': parsed_srt,
|
2019-09-12 20:02:58 +02:00
|
|
|
'root_path': root_path,
|
2018-10-12 23:00:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
def render(self, builder, context):
|
|
|
|
ctx = self.get_context_data()
|
|
|
|
ctx.update(context)
|
2018-10-14 20:52:05 +02:00
|
|
|
builder.render('song.html', self.path, ctx)
|
2018-10-12 23:00:20 +02:00
|
|
|
|
|
|
|
|
|
|
|
class Builder:
|
2019-09-12 20:02:58 +02:00
|
|
|
def __init__(self, root_folder, libreto):
|
2018-10-12 23:00:20 +02:00
|
|
|
self.root_folder = root_folder
|
2018-10-14 20:52:05 +02:00
|
|
|
self.libreto = libreto
|
2018-10-12 23:00:20 +02:00
|
|
|
self.static_dir = Path(__file__).parent / 'static'
|
|
|
|
|
|
|
|
self.env = Environment(
|
|
|
|
loader=PackageLoader('negromateweb', 'templates'),
|
|
|
|
autoescape=select_autoescape(['html']),
|
|
|
|
)
|
|
|
|
self.env.filters['url'] = self.url
|
2019-12-07 12:24:28 +01:00
|
|
|
self.env.filters['display_boolean'] = self.display_boolean
|
2019-09-12 20:02:58 +02:00
|
|
|
self.current_path = self.root_folder
|
2018-10-12 23:00:20 +02:00
|
|
|
|
|
|
|
def url(self, path):
|
2019-09-12 20:02:58 +02:00
|
|
|
return os.path.relpath(path, self.current_path)
|
2018-10-12 23:00:20 +02:00
|
|
|
|
2019-12-07 12:24:28 +01:00
|
|
|
def display_boolean(self, value):
|
|
|
|
if value:
|
|
|
|
return Markup('✓')
|
|
|
|
else:
|
|
|
|
return Markup('✗')
|
|
|
|
|
2018-10-12 23:00:20 +02:00
|
|
|
def render(self, template, target, context):
|
|
|
|
html_file = target / 'index.html'
|
|
|
|
page_template = self.env.get_template(template)
|
2019-09-12 20:02:58 +02:00
|
|
|
root_path = os.path.relpath(self.root_folder, target)
|
|
|
|
context['root_path'] = root_path
|
2018-10-12 23:00:20 +02:00
|
|
|
|
|
|
|
with html_file.open('w') as page:
|
|
|
|
page.write(page_template.render(context))
|
|
|
|
|
|
|
|
def build(self):
|
2018-10-14 20:52:05 +02:00
|
|
|
songs = []
|
2019-12-07 12:24:28 +01:00
|
|
|
pending_songs = []
|
2018-10-12 23:00:20 +02:00
|
|
|
for entry in self.root_folder.iterdir():
|
2019-12-07 12:24:28 +01:00
|
|
|
if entry.name in ['static', 'playlist', 'home', 'todo']:
|
2018-10-12 23:00:20 +02:00
|
|
|
continue
|
|
|
|
if entry.is_dir():
|
|
|
|
print("building {}".format(entry.name))
|
|
|
|
try:
|
2019-09-12 20:02:58 +02:00
|
|
|
songpage = SongPage(entry, self.root_folder)
|
2018-10-12 23:00:20 +02:00
|
|
|
except Exception as e:
|
|
|
|
raise e
|
|
|
|
print("Error: {}".format(e))
|
|
|
|
continue
|
2019-12-07 12:24:28 +01:00
|
|
|
if songpage.publish:
|
|
|
|
songs.append(songpage)
|
|
|
|
else:
|
|
|
|
pending_songs.append(songpage)
|
2018-10-12 23:00:20 +02:00
|
|
|
|
2018-10-27 01:57:57 +02:00
|
|
|
songs.sort(key=lambda a: a.name)
|
2018-10-12 23:00:20 +02:00
|
|
|
global_context = {
|
2018-10-14 20:52:05 +02:00
|
|
|
'songs': songs,
|
2019-09-12 20:02:58 +02:00
|
|
|
'root_folder': self.root_folder,
|
2018-10-12 23:00:20 +02:00
|
|
|
}
|
|
|
|
|
2018-10-14 20:52:05 +02:00
|
|
|
for song in songs:
|
2019-09-12 20:02:58 +02:00
|
|
|
self.current_path = song.path
|
2018-10-14 20:52:05 +02:00
|
|
|
song.render(self, global_context)
|
|
|
|
|
|
|
|
self.render('index.html', self.root_folder, global_context)
|
|
|
|
|
|
|
|
home = self.root_folder / 'home'
|
2019-09-12 20:02:58 +02:00
|
|
|
self.current_path = home
|
2018-10-14 20:52:05 +02:00
|
|
|
if not home.exists():
|
|
|
|
home.mkdir()
|
|
|
|
self.render('home.html', home, global_context)
|
2018-10-12 23:00:20 +02:00
|
|
|
|
2018-10-14 20:52:05 +02:00
|
|
|
playlist = self.root_folder / 'playlist'
|
2019-09-12 20:02:58 +02:00
|
|
|
self.current_path = playlist
|
2018-10-14 20:52:05 +02:00
|
|
|
if not playlist.exists():
|
|
|
|
playlist.mkdir()
|
|
|
|
|
|
|
|
self.render('playlist.html', playlist, global_context)
|
2018-10-12 23:00:20 +02:00
|
|
|
|
2019-12-07 12:24:28 +01:00
|
|
|
todo = self.root_folder / 'todo'
|
|
|
|
self.current_path = todo
|
|
|
|
if not todo.exists():
|
|
|
|
todo.mkdir()
|
|
|
|
todo_context = {
|
|
|
|
'pending_songs': pending_songs,
|
|
|
|
}
|
|
|
|
todo_context.update(global_context)
|
|
|
|
self.render('todo.html', todo, todo_context)
|
|
|
|
|
2018-10-12 23:00:20 +02:00
|
|
|
static = self.root_folder / 'static'
|
|
|
|
|
|
|
|
if not static.exists():
|
2018-10-14 20:52:05 +02:00
|
|
|
static.mkdir()
|
2018-10-12 23:00:20 +02:00
|
|
|
|
|
|
|
subprocess.check_call([
|
|
|
|
'rsync',
|
|
|
|
'-ra',
|
|
|
|
str(self.static_dir),
|
|
|
|
str(self.root_folder.absolute()),
|
|
|
|
])
|
2018-10-14 20:52:05 +02:00
|
|
|
|
|
|
|
libreto = self.root_folder / 'static/libreto/libreto.pdf'
|
|
|
|
copy(str(self.libreto.absolute()), str(libreto.absolute()))
|