165 lines
5.4 KiB
Python
165 lines
5.4 KiB
Python
|
import json
|
||
|
import os
|
||
|
import subprocess
|
||
|
|
||
|
import asstosrt
|
||
|
import srt
|
||
|
import webvtt
|
||
|
|
||
|
from .utils import needs_change
|
||
|
|
||
|
|
||
|
class SongPage:
|
||
|
THUMBNAIL_GEOMETRY = '200x200'
|
||
|
|
||
|
def __init__(self, path, root):
|
||
|
self.name = path.name
|
||
|
self.metadata = None
|
||
|
self.original = None
|
||
|
self.author = None
|
||
|
self.path = path
|
||
|
self.root = root
|
||
|
self.video = None
|
||
|
self.video_type = None
|
||
|
self.vtt = None
|
||
|
self.srt = None
|
||
|
self.karaoke_ass = 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 == "{}.srt".format(self.path.name):
|
||
|
self.srt = entry
|
||
|
self.files.append(entry)
|
||
|
elif entry.name == "{}.karaoke.ass".format(self.path.name):
|
||
|
self.karaoke_ass = entry
|
||
|
self.files.append(entry)
|
||
|
elif entry.name == "{}.ass".format(self.path.name):
|
||
|
self.ass = entry
|
||
|
self.files.append(entry)
|
||
|
elif entry.name == 'thumb.jpg':
|
||
|
self.thumbnail = entry
|
||
|
elif entry.name == 'cover.jpg':
|
||
|
self.cover = entry
|
||
|
elif entry.name == 'index.html':
|
||
|
continue
|
||
|
else:
|
||
|
self.files.append(entry)
|
||
|
|
||
|
srt = self.path / "{}.srt".format(self.path.name)
|
||
|
if needs_change(srt, (self.ass,)):
|
||
|
self.srt = srt
|
||
|
with self.ass.open('r') as assfile, self.srt.open('w') as srtfile:
|
||
|
srtfile.write(asstosrt.convert(assfile))
|
||
|
self.files.append(self.srt)
|
||
|
|
||
|
vtt = self.path / "{}.vtt".format(self.path.name)
|
||
|
if needs_change(vtt, (self.srt,)):
|
||
|
self.vtt = vtt
|
||
|
webvtt.from_srt(str(self.srt.absolute())).save(str(self.vtt.absolute()))
|
||
|
|
||
|
cover = self.path / "cover.jpg"
|
||
|
if needs_change(cover, (self.video,)):
|
||
|
self.cover = cover
|
||
|
command = [
|
||
|
'ffmpeg',
|
||
|
'-loglevel', 'quiet',
|
||
|
'-i', str(self.video.absolute()),
|
||
|
'-vcodec', 'mjpeg',
|
||
|
'-vframes', '1',
|
||
|
'-an',
|
||
|
'-f', 'rawvideo',
|
||
|
'-ss', '2',
|
||
|
'-y',
|
||
|
str(self.cover.absolute()),
|
||
|
]
|
||
|
subprocess.check_call(command)
|
||
|
|
||
|
thumbnail = self.path / "thumb.jpg"
|
||
|
if needs_change(thumbnail, (self.cover,)):
|
||
|
self.thumbnail = thumbnail
|
||
|
subprocess.check_call([
|
||
|
'convert',
|
||
|
str(self.cover.absolute()),
|
||
|
'-resize', self.THUMBNAIL_GEOMETRY,
|
||
|
str(self.thumbnail.absolute()),
|
||
|
])
|
||
|
|
||
|
@property
|
||
|
def publish(self):
|
||
|
has_subtitles = self.ass or self.srt or self.vtt
|
||
|
has_video = self.video
|
||
|
return has_video and has_subtitles
|
||
|
|
||
|
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))
|
||
|
root_path = os.path.relpath(self.root, self.path)
|
||
|
return {
|
||
|
'song': self,
|
||
|
'parsed_srt': parsed_srt,
|
||
|
'root_path': root_path,
|
||
|
}
|
||
|
|
||
|
def render(self, builder, context):
|
||
|
ctx = self.get_context_data()
|
||
|
ctx.update(context)
|
||
|
builder.render('song.html', self.path, ctx)
|
||
|
|
||
|
|
||
|
def load_songs(root_folder):
|
||
|
songs = []
|
||
|
pending_songs = []
|
||
|
for entry in root_folder.iterdir():
|
||
|
if entry.name in ['static', 'playlist', 'home', 'todo']:
|
||
|
continue
|
||
|
if entry.is_dir():
|
||
|
print("building {}".format(entry.name))
|
||
|
try:
|
||
|
songpage = SongPage(entry, root_folder)
|
||
|
except Exception as e:
|
||
|
raise e
|
||
|
print("Error: {}".format(e))
|
||
|
continue
|
||
|
if songpage.publish:
|
||
|
songs.append(songpage)
|
||
|
else:
|
||
|
pending_songs.append(songpage)
|
||
|
|
||
|
songs.sort(key=lambda a: a.name)
|
||
|
|
||
|
return songs, pending_songs
|