import json import os from shutil import copytree, copy from pathlib import Path, PurePath import subprocess from jinja2 import Template, Environment, PackageLoader, select_autoescape import asstosrt import srt import webvtt class SongPage: THUMBNAIL_GEOMETRY = '200x200' def __init__(self, path): self.name = path.name self.original = None self.author = None self.path = path self.video = None self.video_type = None 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 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' self.files.append(entry) elif entry.name.endswith('vtt'): self.vtt = entry elif entry.name.endswith('srt'): 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) 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) 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: self.cover = self.path / "{}.jpg".format(self.path.name) 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: self.thumbnail = self.path / "{}_thumbnail.jpg".format(self.path.name) subprocess.check_call([ 'convert', str(self.cover.absolute()), '-resize', self.THUMBNAIL_GEOMETRY, str(self.thumbnail.absolute()), ]) def get_context_data(self): 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)) return { 'song': self, 'parsed_srt': parsed_srt, } def render(self, builder, context): ctx = self.get_context_data() ctx.update(context) builder.render('song.html', self.path, ctx) class Builder: def __init__(self, root_folder, libreto, root_path=PurePath('/')): self.root_folder = root_folder self.libreto = libreto self.root_path = root_path self.static_dir = Path(__file__).parent / 'static' self.env = Environment( loader=PackageLoader('negromateweb', 'templates'), autoescape=select_autoescape(['html']), ) self.env.filters['url'] = self.url def url(self, path): return self.root_path / path.relative_to(self.root_folder) def render(self, template, target, context): html_file = target / 'index.html' page_template = self.env.get_template(template) with html_file.open('w') as page: page.write(page_template.render(context)) def build(self): songs = [] for entry in self.root_folder.iterdir(): if entry.name in ['static', 'playlist', 'home']: continue if entry.is_dir(): print("building {}".format(entry.name)) try: songpage = SongPage(entry) except Exception as e: raise e print("Error: {}".format(e)) continue songs.append(songpage) global_context = { 'songs': songs, 'root_path': self.root_path, } for song in songs: song.render(self, global_context) self.render('index.html', self.root_folder, global_context) home = self.root_folder / 'home' if not home.exists(): home.mkdir() self.render('home.html', home, global_context) playlist = self.root_folder / 'playlist' if not playlist.exists(): playlist.mkdir() self.render('playlist.html', playlist, global_context) static = self.root_folder / 'static' if not static.exists(): static.mkdir() subprocess.check_call([ 'rsync', '-ra', str(self.static_dir), str(self.root_folder.absolute()), ]) libreto = self.root_folder / 'static/libreto/libreto.pdf' copy(str(self.libreto.absolute()), str(libreto.absolute()))