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
 | 
					import logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
VERSION = "1.3"
 | 
					VERSION = "1.3"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,60 +1,66 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					NegroMate command line.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import argparse
 | 
					import argparse
 | 
				
			||||||
import configparser
 | 
					import configparser
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import traceback
 | 
					 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
 | 
					import traceback
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    from importlib import metadata
 | 
					    from importlib import metadata
 | 
				
			||||||
except ImportError:  # Python <3.8
 | 
					except ImportError:  # Python <3.8
 | 
				
			||||||
    import importlib_metadata as metadata
 | 
					    import importlib_metadata as metadata
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_FILE = '~/.negromate/config.ini'
 | 
					CONFIG_FILE = "~/.negromate/config.ini"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def main():
 | 
					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
 | 
					        * name: String with the command name. Will be used for
 | 
				
			||||||
              argparse subcommand.
 | 
					          argparse subcommand.
 | 
				
			||||||
            * help_text: String with the help text.
 | 
					        * help_text: String with the help text.
 | 
				
			||||||
            * initial_config: Dict for initial configuration of commands.
 | 
					        * initial_config: Dict for initial configuration of commands.
 | 
				
			||||||
            * options: Function to build the parser of the command. Takes
 | 
					        * options: Function to build the parser of the command. Takes
 | 
				
			||||||
              two parametters, the argparser parser instance for this
 | 
					          two parametters, the argparser parser instance for this
 | 
				
			||||||
              subcommand and the ConfigParser instance with all the
 | 
					          subcommand and the ConfigParser instance with all the
 | 
				
			||||||
              configuration.
 | 
					          configuration.
 | 
				
			||||||
            * run: Function that runs the actual command. Takes two
 | 
					        * run: Function that runs the actual command. Takes two
 | 
				
			||||||
              parametters, the argparse Namespace with the arguments and
 | 
					          parametters, the argparse Namespace with the arguments and
 | 
				
			||||||
              the ConfigParser with all the configuration.
 | 
					          the ConfigParser with all the configuration.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Minimal module example:
 | 
					    Minimal module example:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # hello_world.py
 | 
					        # hello_world.py
 | 
				
			||||||
            name = 'hello'
 | 
					        name = 'hello'
 | 
				
			||||||
            help_text = 'Sample command'
 | 
					        help_text = 'Sample command'
 | 
				
			||||||
            initial_config = {
 | 
					        initial_config = {
 | 
				
			||||||
                'who': 'World',
 | 
					            'who': 'World',
 | 
				
			||||||
            }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            def options(parser, config, **kwargs):
 | 
					        def options(parser, config, **kwargs):
 | 
				
			||||||
                parser.add_argument(
 | 
					            parser.add_argument(
 | 
				
			||||||
                    '-w', '--who', default=config['hello']['who'],
 | 
					                '-w', '--who', default=config['hello']['who'],
 | 
				
			||||||
                    help="Who to say hello, defaults to '{}'".format(config['hello']['who'])
 | 
					                help="Who to say hello, defaults to '{}'".format(config['hello']['who'])
 | 
				
			||||||
                )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            def run(args, **kwargs):
 | 
					        def run(args, **kwargs):
 | 
				
			||||||
                print("Hello {}".format(args.who))
 | 
					            print("Hello {}".format(args.who))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        To add more commands to negromate register 'negromate.commands'
 | 
					    To add more commands to negromate register 'negromate.commands'
 | 
				
			||||||
        entry point in setup.cfg. For example:
 | 
					    entry point in setup.cfg. For example:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            [options.entry_points]
 | 
					        [options.entry_points]
 | 
				
			||||||
                negromate.commands =
 | 
					            negromate.commands =
 | 
				
			||||||
                    hello = negromate.web.commands.hello_world
 | 
					                hello = negromate.web.commands.hello_world
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    commands = []
 | 
					    commands = []
 | 
				
			||||||
| 
						 | 
					@ -64,7 +70,7 @@ def main():
 | 
				
			||||||
    sys.argv = args[:1]
 | 
					    sys.argv = args[:1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Load commands from entry_point
 | 
					    # 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:
 | 
					    for entry_point in entry_points:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            command = entry_point.load()
 | 
					            command = entry_point.load()
 | 
				
			||||||
| 
						 | 
					@ -76,13 +82,13 @@ def main():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Load initial configuration for commands
 | 
					    # Load initial configuration for commands
 | 
				
			||||||
    initial_config = {
 | 
					    initial_config = {
 | 
				
			||||||
        'global': {
 | 
					        "global": {
 | 
				
			||||||
            'song_folder': '~/negro_mate/bideoak/',
 | 
					            "song_folder": "~/negro_mate/bideoak/",
 | 
				
			||||||
            'lyrics_file': '~/negro_mate/libreto/libreto.pdf',
 | 
					            "lyrics_file": "~/negro_mate/libreto/libreto.pdf",
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    for command in commands:
 | 
					    for command in commands:
 | 
				
			||||||
        if hasattr(command, 'initial_config'):
 | 
					        if hasattr(command, "initial_config"):
 | 
				
			||||||
            initial_config[command.name] = command.initial_config
 | 
					            initial_config[command.name] = command.initial_config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Load configuration
 | 
					    # Load configuration
 | 
				
			||||||
| 
						 | 
					@ -92,9 +98,7 @@ def main():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Build parser
 | 
					    # Build parser
 | 
				
			||||||
    parser = argparse.ArgumentParser()
 | 
					    parser = argparse.ArgumentParser()
 | 
				
			||||||
    parser.add_argument(
 | 
					    parser.add_argument("-v", "--verbose", action="store_true", help="Display informational messages.")
 | 
				
			||||||
        '-v', '--verbose', action='store_true',
 | 
					 | 
				
			||||||
        help="Display informational messages.")
 | 
					 | 
				
			||||||
    parser.set_defaults(command=None)
 | 
					    parser.set_defaults(command=None)
 | 
				
			||||||
    subparsers = parser.add_subparsers()
 | 
					    subparsers = parser.add_subparsers()
 | 
				
			||||||
    for command in commands:
 | 
					    for command in commands:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,21 +1,27 @@
 | 
				
			||||||
import sys
 | 
					"""
 | 
				
			||||||
 | 
					Generate configuration file.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
name = 'config'
 | 
					
 | 
				
			||||||
help_text = 'Write the configuration'
 | 
					name = "config"
 | 
				
			||||||
 | 
					help_text = "Write the configuration"
 | 
				
			||||||
initial_config = {
 | 
					initial_config = {
 | 
				
			||||||
    'file': '~/.negromate/config.ini',
 | 
					    "file": "~/.negromate/config.ini",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def options(parser, config, **kwargs):
 | 
					def options(parser, config, **kwargs):
 | 
				
			||||||
    parser.add_argument(
 | 
					    parser.add_argument(
 | 
				
			||||||
        '-f', '--file', type=Path,
 | 
					        "-f",
 | 
				
			||||||
        default=config['config']['file'],
 | 
					        "--file",
 | 
				
			||||||
        help="Configuration file, defaults to {}".format(
 | 
					        type=Path,
 | 
				
			||||||
            config['config']['file']))
 | 
					        default=config["config"]["file"],
 | 
				
			||||||
 | 
					        help=f"Configuration file, defaults to {config['config']['file']}",
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def run(args, config, **kwargs):
 | 
					def run(args, config, **kwargs):
 | 
				
			||||||
    with args.file.expanduser().open('w') as f:
 | 
					    with args.file.expanduser().open("w") as f:
 | 
				
			||||||
        config.write(f)
 | 
					        config.write(f)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,62 +1,71 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					Load songs and generate missing files.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ..loader import load_songs
 | 
					from ..loader import load_songs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
name = 'songs'
 | 
					
 | 
				
			||||||
help_text = 'Update song database'
 | 
					name = "songs"
 | 
				
			||||||
 | 
					help_text = "Update song database"
 | 
				
			||||||
initial_config = {
 | 
					initial_config = {
 | 
				
			||||||
    'generate': 'yes',
 | 
					    "generate": "yes",
 | 
				
			||||||
    'regenerate': 'no',
 | 
					    "regenerate": "no",
 | 
				
			||||||
    'karaoke_template_file': '~/negro_mate/karaoke_templates/karaoke.ass',
 | 
					    "karaoke_template_file": "~/negro_mate/karaoke_templates/karaoke.ass",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def options(parser, config, **kwargs):
 | 
					def options(parser, config, **kwargs):
 | 
				
			||||||
    parser.add_argument(
 | 
					    parser.add_argument(
 | 
				
			||||||
        '-s', '--song_folder', type=Path,
 | 
					        "-s",
 | 
				
			||||||
        default=config['global']['song_folder'],
 | 
					        "--song_folder",
 | 
				
			||||||
        help="Folder with the song database, defaults to {}".format(
 | 
					        type=Path,
 | 
				
			||||||
            config['global']['song_folder']))
 | 
					        default=config["global"]["song_folder"],
 | 
				
			||||||
 | 
					        help=f"Folder with the song database, defaults to {config['global']['song_folder']}",
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    parser.add_argument(
 | 
					    parser.add_argument(
 | 
				
			||||||
        '-g', '--generate', action='store_const', const='yes',
 | 
					        "-g",
 | 
				
			||||||
        default=config['songs']['generate'],
 | 
					        "--generate",
 | 
				
			||||||
        help="Generate missing files, defaults to {}".format(
 | 
					        action="store_const",
 | 
				
			||||||
            config['songs']['generate']))
 | 
					        const="yes",
 | 
				
			||||||
 | 
					        default=config["songs"]["generate"],
 | 
				
			||||||
 | 
					        help=f"Generate missing files, defaults to {config['songs']['generate']}",
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    parser.add_argument(
 | 
					    parser.add_argument(
 | 
				
			||||||
        '-r', '--regenerate', action='store_const', const='yes',
 | 
					        "-r",
 | 
				
			||||||
        default=config['songs']['regenerate'],
 | 
					        "--regenerate",
 | 
				
			||||||
        help="Regenerate missing files, defaults to {}".format(
 | 
					        action="store_const",
 | 
				
			||||||
            config['songs']['regenerate']))
 | 
					        const="yes",
 | 
				
			||||||
 | 
					        default=config["songs"]["regenerate"],
 | 
				
			||||||
 | 
					        help=f"Regenerate missing files, defaults to {config['songs']['regenerate']}",
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    parser.add_argument(
 | 
					    parser.add_argument(
 | 
				
			||||||
        '-k', '--karaoke-template', type=Path,
 | 
					        "-k",
 | 
				
			||||||
        default=config['songs']['karaoke_template_file'],
 | 
					        "--karaoke-template",
 | 
				
			||||||
        help="Ass file with the karaoke template, defaults to {}".format(
 | 
					        type=Path,
 | 
				
			||||||
            config['songs']['karaoke_template_file']))
 | 
					        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):
 | 
					def run(args, **kwargs):
 | 
				
			||||||
    generate = args.generate == 'yes'
 | 
					    generate = args.generate == "yes"
 | 
				
			||||||
    regenerate = args.regenerate == 'yes'
 | 
					    regenerate = args.regenerate == "yes"
 | 
				
			||||||
    songs, pending_songs = load_songs(
 | 
					    songs, pending_songs = load_songs(
 | 
				
			||||||
        root_folder=args.song_folder.expanduser(),
 | 
					        root_folder=args.song_folder.expanduser(),
 | 
				
			||||||
        generate=generate, regenerate=regenerate,
 | 
					        generate=generate,
 | 
				
			||||||
        karaoke_template_file=args.karaoke_template.expanduser())
 | 
					        regenerate=regenerate,
 | 
				
			||||||
 | 
					        karaoke_template_file=args.karaoke_template.expanduser(),
 | 
				
			||||||
    print(
 | 
					 | 
				
			||||||
        "#######\n"
 | 
					 | 
				
			||||||
        " Songs\n"
 | 
					 | 
				
			||||||
        "#######"
 | 
					 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print("#######\n Songs\n#######")
 | 
				
			||||||
    for s in songs:
 | 
					    for s in songs:
 | 
				
			||||||
        print(s.name)
 | 
					        print(s.name)
 | 
				
			||||||
    print(
 | 
					    print("###############\n Pending songs\n###############")
 | 
				
			||||||
        "###############\n"
 | 
					 | 
				
			||||||
        " Pending songs\n"
 | 
					 | 
				
			||||||
        "###############"
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    for s in pending_songs:
 | 
					    for s in pending_songs:
 | 
				
			||||||
        print(s.name)
 | 
					        print(s.name)
 | 
				
			||||||
    total_songs = len(songs)
 | 
					    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
 | 
					    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 pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from negromate.songs.utils import generate_cover, generate_thumbnail
 | 
					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):
 | 
					def options(parser, **kwargs):
 | 
				
			||||||
    parser.add_argument(
 | 
					    parser.add_argument("video", help="Video of the song.", type=Path)
 | 
				
			||||||
        'video', help="Video of the song.", type=Path)
 | 
					    parser.add_argument("second", type=int, help="Take snapshot at this second.")
 | 
				
			||||||
    parser.add_argument(
 | 
					 | 
				
			||||||
        'second', type=int,
 | 
					 | 
				
			||||||
        help='Take snapshot at this second.')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def run(args, **kwargs):
 | 
					def run(args, **kwargs):
 | 
				
			||||||
    video = args.video
 | 
					    video = args.video
 | 
				
			||||||
    cover = video.parent / 'cover.jpg'
 | 
					    cover = video.parent / "cover.jpg"
 | 
				
			||||||
    thumbnail = video.parent / 'thumb.jpg'
 | 
					    thumbnail = video.parent / "thumb.jpg"
 | 
				
			||||||
    generate_cover(video, cover, args.second)
 | 
					    generate_cover(video, cover, args.second)
 | 
				
			||||||
    generate_thumbnail(cover, thumbnail)
 | 
					    generate_thumbnail(cover, thumbnail)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,10 +7,10 @@ import ass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@contextmanager
 | 
					@contextmanager
 | 
				
			||||||
def Xephyr_env(display=":2", *args, **kwargs):
 | 
					def xephyr_env(display=":2", *args, **kwargs):
 | 
				
			||||||
    env = os.environ.copy()
 | 
					    env = os.environ.copy()
 | 
				
			||||||
    xephyr = subprocess.Popen(["Xephyr", display], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
 | 
					    xephyr = subprocess.Popen(["Xephyr", display], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
 | 
				
			||||||
    env['DISPLAY'] = display
 | 
					    env["DISPLAY"] = display
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        yield env
 | 
					        yield env
 | 
				
			||||||
    finally:
 | 
					    finally:
 | 
				
			||||||
| 
						 | 
					@ -21,7 +21,7 @@ def set_template(template_subtitles, orig_file, target_file=None):
 | 
				
			||||||
    if target_file is None:
 | 
					    if target_file is None:
 | 
				
			||||||
        target_file = orig_file
 | 
					        target_file = orig_file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with open(orig_file, 'r') as orig:
 | 
					    with open(orig_file, "r") as orig:
 | 
				
			||||||
        subtitles = ass.parse(orig)
 | 
					        subtitles = ass.parse(orig)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    new_events = []
 | 
					    new_events = []
 | 
				
			||||||
| 
						 | 
					@ -29,15 +29,15 @@ def set_template(template_subtitles, orig_file, target_file=None):
 | 
				
			||||||
        new_events.append(dialogue)
 | 
					        new_events.append(dialogue)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for dialogue in subtitles.events:
 | 
					    for dialogue in subtitles.events:
 | 
				
			||||||
        if dialogue.effect.startswith('code'):
 | 
					        if dialogue.effect.startswith("code"):
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
        if dialogue.effect.startswith('template'):
 | 
					        if dialogue.effect.startswith("template"):
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
        new_events.append(dialogue)
 | 
					        new_events.append(dialogue)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    subtitles.events = new_events
 | 
					    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)
 | 
					        subtitles.dump_file(target)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -74,19 +74,35 @@ def apply_template(subtitles, env):
 | 
				
			||||||
def update_karaoke_songs(songs, template_file, regenerate=False):
 | 
					def update_karaoke_songs(songs, template_file, regenerate=False):
 | 
				
			||||||
    from negromate.songs.utils import needs_change
 | 
					    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)
 | 
					        template_subtitles = ass.parse(template)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with Xephyr_env() as env:
 | 
					    with xephyr_env() as env:
 | 
				
			||||||
        for song in songs:
 | 
					        for song in songs:
 | 
				
			||||||
            if song.metadata.get('karaoke'):
 | 
					            if song.metadata.get("karaoke"):
 | 
				
			||||||
                target = song.path / "{}.karaoke.ass".format(song.path.name)
 | 
					                target = song.path / "{}.karaoke.ass".format(song.path.name)
 | 
				
			||||||
                if regenerate or needs_change(target, (song.ass, template_file)):
 | 
					                if regenerate or needs_change(target, (song.ass, template_file)):
 | 
				
			||||||
                    set_template(
 | 
					                    set_template(
 | 
				
			||||||
                        template_subtitles=template_subtitles,
 | 
					                        template_subtitles=template_subtitles, orig_file=str(song.ass), target_file=str(target)
 | 
				
			||||||
                        orig_file=str(song.ass),
 | 
					 | 
				
			||||||
                        target_file=str(target)
 | 
					 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                    time.sleep(2)
 | 
					                    time.sleep(2)
 | 
				
			||||||
                    apply_template(str(target), env)
 | 
					                    apply_template(str(target), env)
 | 
				
			||||||
                    time.sleep(2)
 | 
					                    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 json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import asstosrt
 | 
					import asstosrt
 | 
				
			||||||
import webvtt
 | 
					import webvtt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .utils import needs_change, generate_cover, generate_thumbnail, generate_karaoke_ass
 | 
					 | 
				
			||||||
from . import logger
 | 
					from . import logger
 | 
				
			||||||
 | 
					from .karaoke_templates import generate_karaoke_ass
 | 
				
			||||||
 | 
					from .utils import generate_cover, generate_thumbnail, needs_change
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Song:
 | 
					class Song:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Python representation of a song.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, path, root):
 | 
					    def __init__(self, path, root):
 | 
				
			||||||
        self.name = path.name
 | 
					        self.name = path.name
 | 
				
			||||||
        self.metadata = None
 | 
					        self.metadata = None
 | 
				
			||||||
| 
						 | 
					@ -28,109 +37,129 @@ class Song:
 | 
				
			||||||
        self.search_media()
 | 
					        self.search_media()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def search_media(self):
 | 
					    def search_media(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Initialize song attributes.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        for entry in self.path.iterdir():
 | 
					        for entry in self.path.iterdir():
 | 
				
			||||||
            if entry.name == 'metadata.json':
 | 
					            if entry.name == "metadata.json":
 | 
				
			||||||
                with entry.open('r') as metadatafile:
 | 
					                with entry.open("r") as metadatafile:
 | 
				
			||||||
                    self.metadata = json.load(metadatafile)
 | 
					                    self.metadata = json.load(metadatafile)
 | 
				
			||||||
                if 'name' in self.metadata:
 | 
					                self.name = self.metadata.get("name", self.path.name)
 | 
				
			||||||
                    self.name = self.metadata['name']
 | 
					                self.original = self.metadata.get("original", None)
 | 
				
			||||||
                if 'original' in self.metadata:
 | 
					                self.author = self.metadata.get("author", None)
 | 
				
			||||||
                    self.original = self.metadata['original']
 | 
					                self.date = self.metadata.get("date", None)
 | 
				
			||||||
                if 'author' in self.metadata:
 | 
					            elif entry.name.endswith("mp4"):
 | 
				
			||||||
                    self.author = self.metadata['author']
 | 
					 | 
				
			||||||
                if 'date' in self.metadata:
 | 
					 | 
				
			||||||
                    self.date = self.metadata['date']
 | 
					 | 
				
			||||||
            elif entry.name.endswith('mp4'):
 | 
					 | 
				
			||||||
                self.video = entry
 | 
					                self.video = entry
 | 
				
			||||||
                self.video_type = 'video/mp4'
 | 
					                self.video_type = "video/mp4"
 | 
				
			||||||
                self.files.append(entry)
 | 
					                self.files.append(entry)
 | 
				
			||||||
            elif entry.name.endswith('webm'):
 | 
					            elif entry.name.endswith("webm"):
 | 
				
			||||||
                self.video = entry
 | 
					                self.video = entry
 | 
				
			||||||
                self.video_type = 'video/webm'
 | 
					                self.video_type = "video/webm"
 | 
				
			||||||
                self.files.append(entry)
 | 
					                self.files.append(entry)
 | 
				
			||||||
            elif entry.name.endswith('ogv'):
 | 
					            elif entry.name.endswith("ogv"):
 | 
				
			||||||
                self.video = entry
 | 
					                self.video = entry
 | 
				
			||||||
                self.video_type = 'video/ogg'
 | 
					                self.video_type = "video/ogg"
 | 
				
			||||||
                self.files.append(entry)
 | 
					                self.files.append(entry)
 | 
				
			||||||
            elif entry.name.endswith('vtt'):
 | 
					            elif entry.name.endswith("vtt"):
 | 
				
			||||||
                self.vtt = entry
 | 
					                self.vtt = entry
 | 
				
			||||||
            elif entry.name == "{}.srt".format(self.path.name):
 | 
					            elif entry.name == f"{self.path.name}.srt":
 | 
				
			||||||
                self.srt = entry
 | 
					                self.srt = entry
 | 
				
			||||||
                self.files.append(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.karaoke_ass = entry
 | 
				
			||||||
                self.files.append(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.ass = entry
 | 
				
			||||||
                self.files.append(entry)
 | 
					                self.files.append(entry)
 | 
				
			||||||
            elif entry.name == 'thumb.jpg':
 | 
					            elif entry.name == "thumb.jpg":
 | 
				
			||||||
                self.thumbnail = entry
 | 
					                self.thumbnail = entry
 | 
				
			||||||
            elif entry.name == 'cover.jpg':
 | 
					            elif entry.name == "cover.jpg":
 | 
				
			||||||
                self.cover = entry
 | 
					                self.cover = entry
 | 
				
			||||||
            elif entry.name == 'index.html':
 | 
					            elif entry.name == "index.html":
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                self.files.append(entry)
 | 
					                self.files.append(entry)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def generate_missing(self, regenerate=False, karaoke_template_file=None):
 | 
					    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,)):
 | 
					        if regenerate or needs_change(srt_, (self.ass,)):
 | 
				
			||||||
            logger.info("generating {}".format(srt_))
 | 
					            logger.info("generating %s", str(srt_))
 | 
				
			||||||
            self.srt = 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))
 | 
					                srtfile.write(asstosrt.convert(assfile))
 | 
				
			||||||
            self.files.append(self.srt)
 | 
					            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,)):
 | 
					        if regenerate or needs_change(vtt, (self.srt,)):
 | 
				
			||||||
            logger.info("generating {}".format(vtt))
 | 
					            logger.info("generating %s", str(vtt))
 | 
				
			||||||
            self.vtt = vtt
 | 
					            self.vtt = vtt
 | 
				
			||||||
            webvtt.from_srt(str(self.srt.absolute())).save(str(self.vtt.absolute()))
 | 
					            webvtt.from_srt(str(self.srt.absolute())).save(str(self.vtt.absolute()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cover = self.path / "cover.jpg"
 | 
					        cover = self.path / "cover.jpg"
 | 
				
			||||||
        if regenerate or needs_change(cover, (self.video,)):
 | 
					        if regenerate or needs_change(cover, (self.video,)):
 | 
				
			||||||
            logger.info("generating {}".format(cover))
 | 
					            logger.info("generating %s", str(cover))
 | 
				
			||||||
            self.cover = cover
 | 
					            self.cover = cover
 | 
				
			||||||
            generate_cover(self.video, self.cover)
 | 
					            generate_cover(self.video, self.cover)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        thumbnail = self.path / "thumb.jpg"
 | 
					        thumbnail = self.path / "thumb.jpg"
 | 
				
			||||||
        if regenerate or needs_change(thumbnail, (self.cover,)):
 | 
					        if regenerate or needs_change(thumbnail, (self.cover,)):
 | 
				
			||||||
            logger.info("generating {}".format(thumbnail))
 | 
					            logger.info("generating %s", str(thumbnail))
 | 
				
			||||||
            self.thumbnail = thumbnail
 | 
					            self.thumbnail = thumbnail
 | 
				
			||||||
            generate_thumbnail(self.cover, self.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 = (
 | 
					        karaoke_requirements = (
 | 
				
			||||||
            self.metadata.get('karaoke', False),
 | 
					            self.metadata.get("karaoke", False),
 | 
				
			||||||
            regenerate or needs_change(karaoke_ass, (self.ass, karaoke_template_file)),
 | 
					            regenerate or needs_change(karaoke_ass, (self.ass, karaoke_template_file)),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        if all(karaoke_requirements):
 | 
					        if all(karaoke_requirements):
 | 
				
			||||||
            logger.info("generating {}".format(karaoke_ass))
 | 
					            logger.info("generating %s", str(karaoke_ass))
 | 
				
			||||||
            self.karaoke_ass = karaoke_ass
 | 
					            self.karaoke_ass = karaoke_ass
 | 
				
			||||||
            generate_karaoke_ass(str(karaoke_template_file), str(self.ass), str(karaoke_ass))
 | 
					            generate_karaoke_ass(str(karaoke_template_file), str(self.ass), str(karaoke_ass))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def has_subtitles(self):
 | 
					    def has_subtitles(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        True if the song has any type of subtitles.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        return self.ass or self.srt or self.vtt
 | 
					        return self.ass or self.srt or self.vtt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def publish(self):
 | 
					    def publish(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        True if the song can be published.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        return self.video and self.has_subtitles
 | 
					        return self.video and self.has_subtitles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def pending(self):
 | 
					    def pending(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        True if the song has a video and ass subtitles.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        finished = self.ass and self.video
 | 
					        finished = self.ass and self.video
 | 
				
			||||||
        return not finished
 | 
					        return not finished
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def load_songs(root_folder, generate=True, regenerate=False, karaoke_template_file=None):
 | 
					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 = []
 | 
					    songs = []
 | 
				
			||||||
    pending_songs = []
 | 
					    pending_songs = []
 | 
				
			||||||
    for entry in root_folder.iterdir():
 | 
					    for entry in root_folder.iterdir():
 | 
				
			||||||
        if entry.name in ['static', 'playlist', 'home', 'todo']:
 | 
					        if entry.name in ["static", "playlist", "home", "todo"]:
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
        if entry.is_dir() and (entry / 'metadata.json').exists():
 | 
					        if entry.is_dir() and (entry / "metadata.json").exists():
 | 
				
			||||||
            logger.info("building {}".format(entry.name))
 | 
					            logger.info("building %s", str(entry.name))
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                song = Song(entry, root_folder)
 | 
					                song = Song(entry, root_folder)
 | 
				
			||||||
                if generate:
 | 
					                if generate:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,19 +1,19 @@
 | 
				
			||||||
import subprocess
 | 
					"""
 | 
				
			||||||
import ass
 | 
					Helper functions.
 | 
				
			||||||
import time
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import karaoke_templates
 | 
					import subprocess
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def needs_change(destination, dependencies):
 | 
					def needs_change(destination, dependencies):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Checks if the destination file is older than its dependencies.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    last_dependency_change = 0
 | 
					    last_dependency_change = 0
 | 
				
			||||||
    for dependency in dependencies:
 | 
					    for dependency in dependencies:
 | 
				
			||||||
        if dependency is None:
 | 
					        if dependency is None:
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
        last_dependency_change = max(
 | 
					        last_dependency_change = max(last_dependency_change, dependency.lstat().st_mtime)
 | 
				
			||||||
            last_dependency_change,
 | 
					 | 
				
			||||||
            dependency.lstat().st_mtime
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not destination.exists():
 | 
					    if not destination.exists():
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
| 
						 | 
					@ -22,41 +22,39 @@ def needs_change(destination, dependencies):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def generate_cover(video, cover, second=2):
 | 
					def generate_cover(video, cover, second=2):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Take a snapshot of the video to create a cover file.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    command = [
 | 
					    command = [
 | 
				
			||||||
        'ffmpeg',
 | 
					        "ffmpeg",
 | 
				
			||||||
        '-loglevel', 'quiet',
 | 
					        "-loglevel",
 | 
				
			||||||
        '-i', str(video.absolute()),
 | 
					        "quiet",
 | 
				
			||||||
        '-vcodec', 'mjpeg',
 | 
					        "-i",
 | 
				
			||||||
        '-vframes', '1',
 | 
					        str(video.absolute()),
 | 
				
			||||||
        '-an',
 | 
					        "-vcodec",
 | 
				
			||||||
        '-f', 'rawvideo',
 | 
					        "mjpeg",
 | 
				
			||||||
        '-ss', str(second),
 | 
					        "-vframes",
 | 
				
			||||||
        '-y',
 | 
					        "1",
 | 
				
			||||||
 | 
					        "-an",
 | 
				
			||||||
 | 
					        "-f",
 | 
				
			||||||
 | 
					        "rawvideo",
 | 
				
			||||||
 | 
					        "-ss",
 | 
				
			||||||
 | 
					        str(second),
 | 
				
			||||||
 | 
					        "-y",
 | 
				
			||||||
        str(cover.absolute()),
 | 
					        str(cover.absolute()),
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    subprocess.check_call(command)
 | 
					    subprocess.check_call(command)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def generate_thumbnail(cover, thumbnail, geometry="200x200"):
 | 
					def generate_thumbnail(cover, thumbnail, geometry="200x200"):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Generate a reduced image of the cover.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    command = [
 | 
					    command = [
 | 
				
			||||||
        'convert',
 | 
					        "convert",
 | 
				
			||||||
        str(cover.absolute()),
 | 
					        str(cover.absolute()),
 | 
				
			||||||
        '-resize', geometry,
 | 
					        "-resize",
 | 
				
			||||||
 | 
					        geometry,
 | 
				
			||||||
        str(thumbnail.absolute()),
 | 
					        str(thumbnail.absolute()),
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    subprocess.check_call(command)
 | 
					    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]
 | 
					[build-system]
 | 
				
			||||||
requires = ["setuptools", "wheel"]
 | 
					requires = ["setuptools", "wheel"]
 | 
				
			||||||
build-backend = "setuptools.build_meta"
 | 
					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
 | 
					webvtt-py
 | 
				
			||||||
asstosrt==0.1.6
 | 
					asstosrt==0.1.6
 | 
				
			||||||
srt==1.6.0
 | 
					srt==3.5.3
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue