chore: update dependencies and code cleaning
This commit is contained in:
parent
1dcfce393b
commit
46fb9cff5a
|
@ -0,0 +1,26 @@
|
|||
|
||||
repos:
|
||||
# Using this mirror lets us use mypyc-compiled black, which is about 2x faster
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 24.2.0
|
||||
hooks:
|
||||
- id: black
|
||||
# It is recommended to specify the latest version of Python
|
||||
# supported by your project here, or alternatively use
|
||||
# pre-commit's default_language_version, see
|
||||
# https://pre-commit.com/#top_level-default_language_version
|
||||
language_version: python3.11
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.13.2
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort (python)
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.3.0
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
# - id: ruff-format We don't need this because we have black and isort.
|
|
@ -1,5 +1,10 @@
|
|||
"""
|
||||
Filesystem based database of songs.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
VERSION = "1.3"
|
||||
|
||||
|
||||
|
|
|
@ -1,60 +1,66 @@
|
|||
"""
|
||||
NegroMate command line.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import configparser
|
||||
import logging
|
||||
import traceback
|
||||
import sys
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
try:
|
||||
from importlib import metadata
|
||||
except ImportError: # Python <3.8
|
||||
import importlib_metadata as metadata
|
||||
|
||||
|
||||
CONFIG_FILE = '~/.negromate/config.ini'
|
||||
CONFIG_FILE = "~/.negromate/config.ini"
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Build parser for all the commands and launch appropiate command.
|
||||
Build parser for all the commands and launch appropiate command.
|
||||
|
||||
Each command must be a module with at least the following members:
|
||||
Each command must be a module with at least the following members:
|
||||
|
||||
* name: String with the command name. Will be used for
|
||||
argparse subcommand.
|
||||
* help_text: String with the help text.
|
||||
* initial_config: Dict for initial configuration of commands.
|
||||
* options: Function to build the parser of the command. Takes
|
||||
two parametters, the argparser parser instance for this
|
||||
subcommand and the ConfigParser instance with all the
|
||||
configuration.
|
||||
* run: Function that runs the actual command. Takes two
|
||||
parametters, the argparse Namespace with the arguments and
|
||||
the ConfigParser with all the configuration.
|
||||
* name: String with the command name. Will be used for
|
||||
argparse subcommand.
|
||||
* help_text: String with the help text.
|
||||
* initial_config: Dict for initial configuration of commands.
|
||||
* options: Function to build the parser of the command. Takes
|
||||
two parametters, the argparser parser instance for this
|
||||
subcommand and the ConfigParser instance with all the
|
||||
configuration.
|
||||
* run: Function that runs the actual command. Takes two
|
||||
parametters, the argparse Namespace with the arguments and
|
||||
the ConfigParser with all the configuration.
|
||||
|
||||
Minimal module example:
|
||||
Minimal module example:
|
||||
|
||||
# hello_world.py
|
||||
name = 'hello'
|
||||
help_text = 'Sample command'
|
||||
initial_config = {
|
||||
'who': 'World',
|
||||
}
|
||||
# hello_world.py
|
||||
name = 'hello'
|
||||
help_text = 'Sample command'
|
||||
initial_config = {
|
||||
'who': 'World',
|
||||
}
|
||||
|
||||
def options(parser, config, **kwargs):
|
||||
parser.add_argument(
|
||||
'-w', '--who', default=config['hello']['who'],
|
||||
help="Who to say hello, defaults to '{}'".format(config['hello']['who'])
|
||||
)
|
||||
def options(parser, config, **kwargs):
|
||||
parser.add_argument(
|
||||
'-w', '--who', default=config['hello']['who'],
|
||||
help="Who to say hello, defaults to '{}'".format(config['hello']['who'])
|
||||
)
|
||||
|
||||
def run(args, **kwargs):
|
||||
print("Hello {}".format(args.who))
|
||||
def run(args, **kwargs):
|
||||
print("Hello {}".format(args.who))
|
||||
|
||||
To add more commands to negromate register 'negromate.commands'
|
||||
entry point in setup.cfg. For example:
|
||||
To add more commands to negromate register 'negromate.commands'
|
||||
entry point in setup.cfg. For example:
|
||||
|
||||
[options.entry_points]
|
||||
negromate.commands =
|
||||
hello = negromate.web.commands.hello_world
|
||||
[options.entry_points]
|
||||
negromate.commands =
|
||||
hello = negromate.web.commands.hello_world
|
||||
|
||||
"""
|
||||
commands = []
|
||||
|
@ -64,7 +70,7 @@ def main():
|
|||
sys.argv = args[:1]
|
||||
|
||||
# Load commands from entry_point
|
||||
entry_points = metadata.entry_points().get('negromate.commands', [])
|
||||
entry_points = metadata.entry_points().get("negromate.commands", [])
|
||||
for entry_point in entry_points:
|
||||
try:
|
||||
command = entry_point.load()
|
||||
|
@ -76,13 +82,13 @@ def main():
|
|||
|
||||
# Load initial configuration for commands
|
||||
initial_config = {
|
||||
'global': {
|
||||
'song_folder': '~/negro_mate/bideoak/',
|
||||
'lyrics_file': '~/negro_mate/libreto/libreto.pdf',
|
||||
"global": {
|
||||
"song_folder": "~/negro_mate/bideoak/",
|
||||
"lyrics_file": "~/negro_mate/libreto/libreto.pdf",
|
||||
}
|
||||
}
|
||||
for command in commands:
|
||||
if hasattr(command, 'initial_config'):
|
||||
if hasattr(command, "initial_config"):
|
||||
initial_config[command.name] = command.initial_config
|
||||
|
||||
# Load configuration
|
||||
|
@ -92,9 +98,7 @@ def main():
|
|||
|
||||
# Build parser
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'-v', '--verbose', action='store_true',
|
||||
help="Display informational messages.")
|
||||
parser.add_argument("-v", "--verbose", action="store_true", help="Display informational messages.")
|
||||
parser.set_defaults(command=None)
|
||||
subparsers = parser.add_subparsers()
|
||||
for command in commands:
|
||||
|
|
|
@ -1,21 +1,27 @@
|
|||
import sys
|
||||
"""
|
||||
Generate configuration file.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
name = 'config'
|
||||
help_text = 'Write the configuration'
|
||||
|
||||
name = "config"
|
||||
help_text = "Write the configuration"
|
||||
initial_config = {
|
||||
'file': '~/.negromate/config.ini',
|
||||
"file": "~/.negromate/config.ini",
|
||||
}
|
||||
|
||||
|
||||
def options(parser, config, **kwargs):
|
||||
parser.add_argument(
|
||||
'-f', '--file', type=Path,
|
||||
default=config['config']['file'],
|
||||
help="Configuration file, defaults to {}".format(
|
||||
config['config']['file']))
|
||||
"-f",
|
||||
"--file",
|
||||
type=Path,
|
||||
default=config["config"]["file"],
|
||||
help=f"Configuration file, defaults to {config['config']['file']}",
|
||||
)
|
||||
|
||||
|
||||
def run(args, config, **kwargs):
|
||||
with args.file.expanduser().open('w') as f:
|
||||
with args.file.expanduser().open("w") as f:
|
||||
config.write(f)
|
||||
|
|
|
@ -1,62 +1,71 @@
|
|||
"""
|
||||
Load songs and generate missing files.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from ..loader import load_songs
|
||||
|
||||
name = 'songs'
|
||||
help_text = 'Update song database'
|
||||
|
||||
name = "songs"
|
||||
help_text = "Update song database"
|
||||
initial_config = {
|
||||
'generate': 'yes',
|
||||
'regenerate': 'no',
|
||||
'karaoke_template_file': '~/negro_mate/karaoke_templates/karaoke.ass',
|
||||
"generate": "yes",
|
||||
"regenerate": "no",
|
||||
"karaoke_template_file": "~/negro_mate/karaoke_templates/karaoke.ass",
|
||||
}
|
||||
|
||||
|
||||
def options(parser, config, **kwargs):
|
||||
parser.add_argument(
|
||||
'-s', '--song_folder', type=Path,
|
||||
default=config['global']['song_folder'],
|
||||
help="Folder with the song database, defaults to {}".format(
|
||||
config['global']['song_folder']))
|
||||
"-s",
|
||||
"--song_folder",
|
||||
type=Path,
|
||||
default=config["global"]["song_folder"],
|
||||
help=f"Folder with the song database, defaults to {config['global']['song_folder']}",
|
||||
)
|
||||
parser.add_argument(
|
||||
'-g', '--generate', action='store_const', const='yes',
|
||||
default=config['songs']['generate'],
|
||||
help="Generate missing files, defaults to {}".format(
|
||||
config['songs']['generate']))
|
||||
"-g",
|
||||
"--generate",
|
||||
action="store_const",
|
||||
const="yes",
|
||||
default=config["songs"]["generate"],
|
||||
help=f"Generate missing files, defaults to {config['songs']['generate']}",
|
||||
)
|
||||
parser.add_argument(
|
||||
'-r', '--regenerate', action='store_const', const='yes',
|
||||
default=config['songs']['regenerate'],
|
||||
help="Regenerate missing files, defaults to {}".format(
|
||||
config['songs']['regenerate']))
|
||||
"-r",
|
||||
"--regenerate",
|
||||
action="store_const",
|
||||
const="yes",
|
||||
default=config["songs"]["regenerate"],
|
||||
help=f"Regenerate missing files, defaults to {config['songs']['regenerate']}",
|
||||
)
|
||||
parser.add_argument(
|
||||
'-k', '--karaoke-template', type=Path,
|
||||
default=config['songs']['karaoke_template_file'],
|
||||
help="Ass file with the karaoke template, defaults to {}".format(
|
||||
config['songs']['karaoke_template_file']))
|
||||
"-k",
|
||||
"--karaoke-template",
|
||||
type=Path,
|
||||
default=config["songs"]["karaoke_template_file"],
|
||||
help=f"Ass file with the karaoke template, defaults to {config['songs']['karaoke_template_file']}",
|
||||
)
|
||||
|
||||
|
||||
def run(args, **kwargs):
|
||||
generate = args.generate == 'yes'
|
||||
regenerate = args.regenerate == 'yes'
|
||||
generate = args.generate == "yes"
|
||||
regenerate = args.regenerate == "yes"
|
||||
songs, pending_songs = load_songs(
|
||||
root_folder=args.song_folder.expanduser(),
|
||||
generate=generate, regenerate=regenerate,
|
||||
karaoke_template_file=args.karaoke_template.expanduser())
|
||||
|
||||
print(
|
||||
"#######\n"
|
||||
" Songs\n"
|
||||
"#######"
|
||||
generate=generate,
|
||||
regenerate=regenerate,
|
||||
karaoke_template_file=args.karaoke_template.expanduser(),
|
||||
)
|
||||
|
||||
print("#######\n Songs\n#######")
|
||||
for s in songs:
|
||||
print(s.name)
|
||||
print(
|
||||
"###############\n"
|
||||
" Pending songs\n"
|
||||
"###############"
|
||||
)
|
||||
print("###############\n Pending songs\n###############")
|
||||
for s in pending_songs:
|
||||
print(s.name)
|
||||
total_songs = len(songs)
|
||||
songs_with_karaoke = len(list(s for s in songs if s.metadata['karaoke']))
|
||||
songs_with_karaoke = len(list(s for s in songs if s.metadata["karaoke"]))
|
||||
percent = int(songs_with_karaoke / total_songs * 100) if total_songs else 0
|
||||
print("Total songs: {}. With karaoke: {} ({}%)".format(total_songs, songs_with_karaoke, percent))
|
||||
print(f"Total songs: {total_songs}. With karaoke: {songs_with_karaoke} ({percent}%)")
|
||||
|
|
|
@ -1,22 +1,24 @@
|
|||
"""
|
||||
Generate song images
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from negromate.songs.utils import generate_cover, generate_thumbnail
|
||||
|
||||
name = 'thumbnail'
|
||||
help_text = 'Generate cover and thumbnail for a video.'
|
||||
|
||||
name = "thumbnail"
|
||||
help_text = "Generate cover and thumbnail for a video."
|
||||
|
||||
|
||||
def options(parser, config, **kwargs):
|
||||
parser.add_argument(
|
||||
'video', help="Video of the song.", type=Path)
|
||||
parser.add_argument(
|
||||
'second', type=int,
|
||||
help='Take snapshot at this second.')
|
||||
def options(parser, **kwargs):
|
||||
parser.add_argument("video", help="Video of the song.", type=Path)
|
||||
parser.add_argument("second", type=int, help="Take snapshot at this second.")
|
||||
|
||||
|
||||
def run(args, **kwargs):
|
||||
video = args.video
|
||||
cover = video.parent / 'cover.jpg'
|
||||
thumbnail = video.parent / 'thumb.jpg'
|
||||
cover = video.parent / "cover.jpg"
|
||||
thumbnail = video.parent / "thumb.jpg"
|
||||
generate_cover(video, cover, args.second)
|
||||
generate_thumbnail(cover, thumbnail)
|
||||
|
|
|
@ -7,10 +7,10 @@ import ass
|
|||
|
||||
|
||||
@contextmanager
|
||||
def Xephyr_env(display=":2", *args, **kwargs):
|
||||
def xephyr_env(display=":2", *args, **kwargs):
|
||||
env = os.environ.copy()
|
||||
xephyr = subprocess.Popen(["Xephyr", display], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
env['DISPLAY'] = display
|
||||
env["DISPLAY"] = display
|
||||
try:
|
||||
yield env
|
||||
finally:
|
||||
|
@ -21,7 +21,7 @@ def set_template(template_subtitles, orig_file, target_file=None):
|
|||
if target_file is None:
|
||||
target_file = orig_file
|
||||
|
||||
with open(orig_file, 'r') as orig:
|
||||
with open(orig_file, "r") as orig:
|
||||
subtitles = ass.parse(orig)
|
||||
|
||||
new_events = []
|
||||
|
@ -29,15 +29,15 @@ def set_template(template_subtitles, orig_file, target_file=None):
|
|||
new_events.append(dialogue)
|
||||
|
||||
for dialogue in subtitles.events:
|
||||
if dialogue.effect.startswith('code'):
|
||||
if dialogue.effect.startswith("code"):
|
||||
continue
|
||||
if dialogue.effect.startswith('template'):
|
||||
if dialogue.effect.startswith("template"):
|
||||
continue
|
||||
new_events.append(dialogue)
|
||||
|
||||
subtitles.events = new_events
|
||||
|
||||
with open(target_file, 'w', encoding='utf-8-sig') as target:
|
||||
with open(target_file, "w", encoding="utf-8-sig") as target:
|
||||
subtitles.dump_file(target)
|
||||
|
||||
|
||||
|
@ -74,19 +74,35 @@ def apply_template(subtitles, env):
|
|||
def update_karaoke_songs(songs, template_file, regenerate=False):
|
||||
from negromate.songs.utils import needs_change
|
||||
|
||||
with open(template_file, 'r') as template:
|
||||
with open(template_file, "r") as template:
|
||||
template_subtitles = ass.parse(template)
|
||||
|
||||
with Xephyr_env() as env:
|
||||
with xephyr_env() as env:
|
||||
for song in songs:
|
||||
if song.metadata.get('karaoke'):
|
||||
if song.metadata.get("karaoke"):
|
||||
target = song.path / "{}.karaoke.ass".format(song.path.name)
|
||||
if regenerate or needs_change(target, (song.ass, template_file)):
|
||||
set_template(
|
||||
template_subtitles=template_subtitles,
|
||||
orig_file=str(song.ass),
|
||||
target_file=str(target)
|
||||
template_subtitles=template_subtitles, orig_file=str(song.ass), target_file=str(target)
|
||||
)
|
||||
time.sleep(2)
|
||||
apply_template(str(target), env)
|
||||
time.sleep(2)
|
||||
|
||||
|
||||
def generate_karaoke_ass(template_file, orig_file, target_file):
|
||||
"""
|
||||
Apply ass template to the subtitle file to render animations.
|
||||
"""
|
||||
with open(template_file, "r") as template:
|
||||
template_subtitles = ass.parse(template)
|
||||
|
||||
with xephyr_env() as env:
|
||||
set_template(
|
||||
template_subtitles=template_subtitles,
|
||||
orig_file=orig_file,
|
||||
target_file=target_file,
|
||||
)
|
||||
time.sleep(2)
|
||||
apply_template(target_file, env)
|
||||
time.sleep(2)
|
||||
|
|
|
@ -1,13 +1,22 @@
|
|||
"""
|
||||
Load songs from the root folder.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
import asstosrt
|
||||
import webvtt
|
||||
|
||||
from .utils import needs_change, generate_cover, generate_thumbnail, generate_karaoke_ass
|
||||
from . import logger
|
||||
from .karaoke_templates import generate_karaoke_ass
|
||||
from .utils import generate_cover, generate_thumbnail, needs_change
|
||||
|
||||
|
||||
class Song:
|
||||
"""
|
||||
Python representation of a song.
|
||||
"""
|
||||
|
||||
def __init__(self, path, root):
|
||||
self.name = path.name
|
||||
self.metadata = None
|
||||
|
@ -28,109 +37,129 @@ class Song:
|
|||
self.search_media()
|
||||
|
||||
def search_media(self):
|
||||
"""
|
||||
Initialize song attributes.
|
||||
"""
|
||||
for entry in self.path.iterdir():
|
||||
if entry.name == 'metadata.json':
|
||||
with entry.open('r') as metadatafile:
|
||||
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']
|
||||
if 'date' in self.metadata:
|
||||
self.date = self.metadata['date']
|
||||
elif entry.name.endswith('mp4'):
|
||||
self.name = self.metadata.get("name", self.path.name)
|
||||
self.original = self.metadata.get("original", None)
|
||||
self.author = self.metadata.get("author", None)
|
||||
self.date = self.metadata.get("date", None)
|
||||
elif entry.name.endswith("mp4"):
|
||||
self.video = entry
|
||||
self.video_type = 'video/mp4'
|
||||
self.video_type = "video/mp4"
|
||||
self.files.append(entry)
|
||||
elif entry.name.endswith('webm'):
|
||||
elif entry.name.endswith("webm"):
|
||||
self.video = entry
|
||||
self.video_type = 'video/webm'
|
||||
self.video_type = "video/webm"
|
||||
self.files.append(entry)
|
||||
elif entry.name.endswith('ogv'):
|
||||
elif entry.name.endswith("ogv"):
|
||||
self.video = entry
|
||||
self.video_type = 'video/ogg'
|
||||
self.video_type = "video/ogg"
|
||||
self.files.append(entry)
|
||||
elif entry.name.endswith('vtt'):
|
||||
elif entry.name.endswith("vtt"):
|
||||
self.vtt = entry
|
||||
elif entry.name == "{}.srt".format(self.path.name):
|
||||
elif entry.name == f"{self.path.name}.srt":
|
||||
self.srt = entry
|
||||
self.files.append(entry)
|
||||
elif entry.name == "{}.karaoke.ass".format(self.path.name):
|
||||
elif entry.name == f"{self.path.name}.karaoke.ass":
|
||||
self.karaoke_ass = entry
|
||||
self.files.append(entry)
|
||||
elif entry.name == "{}.ass".format(self.path.name):
|
||||
elif entry.name == f"{self.path.name}.ass":
|
||||
self.ass = entry
|
||||
self.files.append(entry)
|
||||
elif entry.name == 'thumb.jpg':
|
||||
elif entry.name == "thumb.jpg":
|
||||
self.thumbnail = entry
|
||||
elif entry.name == 'cover.jpg':
|
||||
elif entry.name == "cover.jpg":
|
||||
self.cover = entry
|
||||
elif entry.name == 'index.html':
|
||||
elif entry.name == "index.html":
|
||||
continue
|
||||
else:
|
||||
self.files.append(entry)
|
||||
|
||||
def generate_missing(self, regenerate=False, karaoke_template_file=None):
|
||||
srt_ = self.path / "{}.srt".format(self.path.name)
|
||||
"""
|
||||
Generate missing files, if they can be derived from another one.
|
||||
"""
|
||||
srt_ = self.path / f"{self.path.name}.srt"
|
||||
if regenerate or needs_change(srt_, (self.ass,)):
|
||||
logger.info("generating {}".format(srt_))
|
||||
logger.info("generating %s", str(srt_))
|
||||
self.srt = srt_
|
||||
with self.ass.open('r') as assfile, self.srt.open('w') as srtfile:
|
||||
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)
|
||||
vtt = self.path / "{self.path.name}.vtt"
|
||||
if regenerate or needs_change(vtt, (self.srt,)):
|
||||
logger.info("generating {}".format(vtt))
|
||||
logger.info("generating %s", str(vtt))
|
||||
self.vtt = vtt
|
||||
webvtt.from_srt(str(self.srt.absolute())).save(str(self.vtt.absolute()))
|
||||
|
||||
cover = self.path / "cover.jpg"
|
||||
if regenerate or needs_change(cover, (self.video,)):
|
||||
logger.info("generating {}".format(cover))
|
||||
logger.info("generating %s", str(cover))
|
||||
self.cover = cover
|
||||
generate_cover(self.video, self.cover)
|
||||
|
||||
thumbnail = self.path / "thumb.jpg"
|
||||
if regenerate or needs_change(thumbnail, (self.cover,)):
|
||||
logger.info("generating {}".format(thumbnail))
|
||||
logger.info("generating %s", str(thumbnail))
|
||||
self.thumbnail = thumbnail
|
||||
generate_thumbnail(self.cover, self.thumbnail)
|
||||
|
||||
karaoke_ass = self.path / "{}.karaoke.ass".format(self.path.name)
|
||||
karaoke_ass = self.path / f"{self.path.name}.karaoke.ass"
|
||||
karaoke_requirements = (
|
||||
self.metadata.get('karaoke', False),
|
||||
self.metadata.get("karaoke", False),
|
||||
regenerate or needs_change(karaoke_ass, (self.ass, karaoke_template_file)),
|
||||
)
|
||||
if all(karaoke_requirements):
|
||||
logger.info("generating {}".format(karaoke_ass))
|
||||
logger.info("generating %s", str(karaoke_ass))
|
||||
self.karaoke_ass = karaoke_ass
|
||||
generate_karaoke_ass(str(karaoke_template_file), str(self.ass), str(karaoke_ass))
|
||||
|
||||
@property
|
||||
def has_subtitles(self):
|
||||
"""
|
||||
True if the song has any type of subtitles.
|
||||
"""
|
||||
return self.ass or self.srt or self.vtt
|
||||
|
||||
@property
|
||||
def publish(self):
|
||||
"""
|
||||
True if the song can be published.
|
||||
"""
|
||||
return self.video and self.has_subtitles
|
||||
|
||||
@property
|
||||
def pending(self):
|
||||
"""
|
||||
True if the song has a video and ass subtitles.
|
||||
"""
|
||||
finished = self.ass and self.video
|
||||
return not finished
|
||||
|
||||
|
||||
def load_songs(root_folder, generate=True, regenerate=False, karaoke_template_file=None):
|
||||
"""
|
||||
Load songs from root_folder.
|
||||
|
||||
If generate is True missing files will be generated.
|
||||
|
||||
If regenerate is True, the files will be generated again, even if they source has not changed.
|
||||
|
||||
karaoke_template_file can be a path to a ass file with the code to generate subtitle animations.
|
||||
"""
|
||||
songs = []
|
||||
pending_songs = []
|
||||
for entry in root_folder.iterdir():
|
||||
if entry.name in ['static', 'playlist', 'home', 'todo']:
|
||||
if entry.name in ["static", "playlist", "home", "todo"]:
|
||||
continue
|
||||
if entry.is_dir() and (entry / 'metadata.json').exists():
|
||||
logger.info("building {}".format(entry.name))
|
||||
if entry.is_dir() and (entry / "metadata.json").exists():
|
||||
logger.info("building %s", str(entry.name))
|
||||
try:
|
||||
song = Song(entry, root_folder)
|
||||
if generate:
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import subprocess
|
||||
import ass
|
||||
import time
|
||||
"""
|
||||
Helper functions.
|
||||
"""
|
||||
|
||||
from . import karaoke_templates
|
||||
import subprocess
|
||||
|
||||
|
||||
def needs_change(destination, dependencies):
|
||||
"""
|
||||
Checks if the destination file is older than its dependencies.
|
||||
"""
|
||||
last_dependency_change = 0
|
||||
for dependency in dependencies:
|
||||
if dependency is None:
|
||||
return False
|
||||
last_dependency_change = max(
|
||||
last_dependency_change,
|
||||
dependency.lstat().st_mtime
|
||||
)
|
||||
last_dependency_change = max(last_dependency_change, dependency.lstat().st_mtime)
|
||||
|
||||
if not destination.exists():
|
||||
return True
|
||||
|
@ -22,41 +22,39 @@ def needs_change(destination, dependencies):
|
|||
|
||||
|
||||
def generate_cover(video, cover, second=2):
|
||||
"""
|
||||
Take a snapshot of the video to create a cover file.
|
||||
"""
|
||||
command = [
|
||||
'ffmpeg',
|
||||
'-loglevel', 'quiet',
|
||||
'-i', str(video.absolute()),
|
||||
'-vcodec', 'mjpeg',
|
||||
'-vframes', '1',
|
||||
'-an',
|
||||
'-f', 'rawvideo',
|
||||
'-ss', str(second),
|
||||
'-y',
|
||||
"ffmpeg",
|
||||
"-loglevel",
|
||||
"quiet",
|
||||
"-i",
|
||||
str(video.absolute()),
|
||||
"-vcodec",
|
||||
"mjpeg",
|
||||
"-vframes",
|
||||
"1",
|
||||
"-an",
|
||||
"-f",
|
||||
"rawvideo",
|
||||
"-ss",
|
||||
str(second),
|
||||
"-y",
|
||||
str(cover.absolute()),
|
||||
]
|
||||
subprocess.check_call(command)
|
||||
|
||||
|
||||
def generate_thumbnail(cover, thumbnail, geometry="200x200"):
|
||||
"""
|
||||
Generate a reduced image of the cover.
|
||||
"""
|
||||
command = [
|
||||
'convert',
|
||||
"convert",
|
||||
str(cover.absolute()),
|
||||
'-resize', geometry,
|
||||
"-resize",
|
||||
geometry,
|
||||
str(thumbnail.absolute()),
|
||||
]
|
||||
subprocess.check_call(command)
|
||||
|
||||
|
||||
def generate_karaoke_ass(template_file, orig_file, target_file):
|
||||
with open(template_file, 'r') as template:
|
||||
template_subtitles = ass.parse(template)
|
||||
|
||||
with karaoke_templates.Xephyr_env() as env:
|
||||
karaoke_templates.set_template(
|
||||
template_subtitles=template_subtitles,
|
||||
orig_file=orig_file,
|
||||
target_file=target_file,
|
||||
)
|
||||
time.sleep(2)
|
||||
karaoke_templates.apply_template(target_file, env)
|
||||
time.sleep(2)
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.black]
|
||||
line_length = 120
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
lines_after_imports = 2
|
||||
|
||||
[tool.pylint.'MESSAGES CONTROL']
|
||||
max-line-length = 120
|
||||
disable = "invalid-name, unused-wildcard-import, wildcard-import"
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
exclude = [
|
||||
"build",
|
||||
]
|
||||
include = ["negromate/*"]
|
||||
fix = false
|
||||
force-exclude = true
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
webvtt-py
|
||||
asstosrt==0.1.6
|
||||
srt==1.6.0
|
||||
srt==3.5.3
|
||||
|
|
Loading…
Reference in New Issue