From 5628d5d21a0e3995cb7d79189fe77f44ab140f93 Mon Sep 17 00:00:00 2001 From: CoffeeStraw Date: Wed, 6 Feb 2019 17:45:58 +0100 Subject: [PATCH] First Commit --- .gitignore | 8 + README.md | 42 ++ docs/Makefile | 19 + docs/make.bat | 35 + docs/source/_static/PyonFX Logo.png | Bin 0 -> 108936 bytes docs/source/ass utility.rst | 6 + docs/source/conf.py | 167 +++++ docs/source/convert.rst | 6 + docs/source/index.rst | 13 + docs/source/settings.rst | 6 + docs/source/utils.rst | 6 + examples/First Effect.py | 72 ++ pyonfx/__init__.py | 10 + pyonfx/ass_utility.py | 1006 +++++++++++++++++++++++++++ pyonfx/convert.py | 109 +++ pyonfx/font_utility.py | 92 +++ pyonfx/settings.py | 17 + pyonfx/utils.py | 42 ++ setup.py | 21 + tests/Ass/in.ass | 58 ++ tests/__init__.py | 0 tests/test_ass.py | 54 ++ 22 files changed, 1789 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/source/_static/PyonFX Logo.png create mode 100644 docs/source/ass utility.rst create mode 100644 docs/source/conf.py create mode 100644 docs/source/convert.rst create mode 100644 docs/source/index.rst create mode 100644 docs/source/settings.rst create mode 100644 docs/source/utils.rst create mode 100644 examples/First Effect.py create mode 100644 pyonfx/__init__.py create mode 100644 pyonfx/ass_utility.py create mode 100644 pyonfx/convert.py create mode 100644 pyonfx/font_utility.py create mode 100644 pyonfx/settings.py create mode 100644 pyonfx/utils.py create mode 100644 setup.py create mode 100644 tests/Ass/in.ass create mode 100644 tests/__init__.py create mode 100644 tests/test_ass.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ebc0be1e --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.pytest_cache/ +docs/build/ +examples/__pycache__/ +examples/Output.ass +pyonfx/__pycache__/ +tests/Ass - Crazy/ +tests/__pycache__/ +tests/Output.ass \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..1ecc6c77 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +

+ PyonFX Logo +

+ +**PyonFX** is an *easy way* to do **KFX** and **complex typesetting** based on subtitle format **ASS** (Advanced Substation Alpha). + +Powered by **Python3**, **PyonFX** aims to offer stability, efficiency and ease of use +for everyone who wants to create something more visually complex with the ASS format. + +## Getting Started + +TO DO. + +## Installing + +TO DO. + +``` +pip install ... +``` + +## Examples + +*Examples* directory contains templates which are ready to go for testing, for absolute beginners until advanced users. You can load and execute them, look into the code, reuse parts of them, learn by doing. + +## Contributing + +If you want to contribute to PyonFX, be sure to review the [contribution +guidelines](CONTRIBUTING.md) (TO DO). + +This project will use [GitHub issues](link_to_project_issues) for +tracking **requests and bugs**, so please *don't use* issues for general questions and discussion. + +## License + +This project is licensed under the LGPL v3.0 License - see the [LICENSE](LICENSE) file for details. + +## Acknowledgments + +* **Youka** for the original main functions ideas of **NyuFX**; +* **McWhite** for the original functions ideas of his library for **NyuFX**; +* **Siplas** for helping me out in the realization of the logo of **PyonFX**. \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..69fe55ec --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,19 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..4d9eb83d --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/docs/source/_static/PyonFX Logo.png b/docs/source/_static/PyonFX Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d63a279cdd0e941155cdf3794051e721e744d035 GIT binary patch literal 108936 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 UIzBemVE!*Jt@5N)(lq%014vm56951J literal 0 HcmV?d00001 diff --git a/docs/source/ass utility.rst b/docs/source/ass utility.rst new file mode 100644 index 00000000..91aebc83 --- /dev/null +++ b/docs/source/ass utility.rst @@ -0,0 +1,6 @@ +This file contains all the functions and class definitions to work with the ASS format. + +PyonFX Ass Utility +================== +.. automodule:: pyonfx.ass_utility + :members: \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 00000000..86010ff3 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. + +import os +import sys +import sphinx_rtd_theme + +# Updating path +sys.path.insert(0, os.path.abspath('..//..')) +sys.setrecursionlimit(1500) + +from pyonfx import __version__ + +# -- Project information ----------------------------------------------------- + +project = 'PyonFX' +copyright = '2019, Antonio Strippoli' +author = 'Antonio Strippoli (CoffeeStraw/YellowFlash)' + +# The short X.Y version +version = '' +# The full version, including alpha/beta/rc tags +release = __version__ + + +# -- General configuration --------------------------------------------------- +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode', + 'sphinx.ext.githubpages', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = None + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'sphinx_rtd_theme' +htm_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# Theme options are theme-specific and customize the look and feel of a theme +# further. +html_theme_options = { + 'canonical_url': '', + 'logo_only': True, + 'display_version': True, + 'prev_next_buttons_location': 'bottom', + 'style_external_links': True, + 'collapse_navigation': True, + 'sticky_navigation': True, + 'navigation_depth': 4, + 'includehidden': True, + 'titles_only': False +} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + +html_logo = "_static/PyonFX Logo.png" + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'PyonFXdoc' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + 'papersize': 'letterpaper', + 'pointsize': '10pt', + 'preamble': '', + 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'PyonFX.tex', 'PyonFX Documentation', + 'Antonio Strippoli (CoffeeStraw/YellowFlash)', 'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'pyonfx', 'PyonFX Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'PyonFX', 'PyonFX Documentation', + author, 'PyonFX', 'One line description of project.', + 'Miscellaneous'), +] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + + +# -- Extension configuration ------------------------------------------------- +extensions = ['sphinxcontrib.napoleon'] + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/': None} diff --git a/docs/source/convert.rst b/docs/source/convert.rst new file mode 100644 index 00000000..80d995ec --- /dev/null +++ b/docs/source/convert.rst @@ -0,0 +1,6 @@ +This file contains the Convert class definition. + +PyonFX Convert Functions +======================== +.. automodule:: pyonfx.convert + :members: \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 00000000..1f4aee09 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,13 @@ +.. toctree:: + :hidden: + :maxdepth: 2 + + ass utility + convert + utils + settings + +The PyonFX API Reference +************************ + +.. automodule:: pyonfx \ No newline at end of file diff --git a/docs/source/settings.rst b/docs/source/settings.rst new file mode 100644 index 00000000..6c34f7e8 --- /dev/null +++ b/docs/source/settings.rst @@ -0,0 +1,6 @@ +This file contains some settings that can be customized by the user in the top of their .py file. + +PyonFX Settings +=============== +.. automodule:: pyonfx.settings + :members: \ No newline at end of file diff --git a/docs/source/utils.rst b/docs/source/utils.rst new file mode 100644 index 00000000..2b03b1cf --- /dev/null +++ b/docs/source/utils.rst @@ -0,0 +1,6 @@ +This file contains the Utils class definition. + +PyonFX Utils +============ +.. automodule:: pyonfx.utils + :members: \ No newline at end of file diff --git a/examples/First Effect.py b/examples/First Effect.py new file mode 100644 index 00000000..c1031dfd --- /dev/null +++ b/examples/First Effect.py @@ -0,0 +1,72 @@ +# If you're trying this example having downloaded the repository +# and not only installed PyonFX, uncomment the following lines: +# import sys +# sys.path.insert(0,'../') + +from pyonfx import * + +io = Ass("..\\tests\\Ass\\in.ass") +meta, styles, lines = io.get_data() + +def romaji_kanji(line, l): + # Leadin Effect + for syli, syl in Utils.all_non_empty(line.syls): + l.layer = 0 + + l.start_time = line.start_time - line.leadin/2 + l.end_time = line.start_time + syl.start_time + l.dur = l.end_time - l.start_time + + l.text = "{\\an5\\pos(%.3f,%.3f)\\fad(%d,0)}%s" % ( + syl.center, syl.middle, line.leadin/2, syl.text) + + io.write_line(l) + + # Main Effect + for syli, syl in Utils.all_non_empty(line.syls): + l.layer = 1 + + l.start_time = line.start_time + syl.start_time + l.end_time = line.start_time + syl.end_time + l.dur = l.end_time - l.start_time + + l.text = "{\\an5\\pos(%.3f,%.3f)"\ + "\\t(0,%d,0.5,\\1c&HFFFFFF&\\3c&HABABAB&\\fscx125\\fscy125)"\ + "\\t(%d,%d,1.5,\\fscx100\\fscy100\\1c%s\\3c%s)}%s" % ( + syl.center, syl.middle, + l.dur/3, l.dur/3, l.dur, line.styleref.color1, line.styleref.color3, syl.text) + + io.write_line(l) + + # Leadout Effect + for syli, syl in Utils.all_non_empty(line.syls): + l.layer = 0 + + l.start_time = line.start_time + syl.end_time + l.end_time = line.end_time + line.leadout/2 + l.dur = l.end_time - l.start_time + + l.text = "{\\an5\\pos(%.3f,%.3f)\\fad(0,%d)}%s" % ( + syl.center, syl.middle, line.leadout/2, syl.text) + + io.write_line(l) + +def lyrics(line, l): + # Translation Effect + l.start_time = line.start_time - line.leadin/2 + l.end_time = line.end_time + line.leadout/2 + l.dur = l.end_time - l.start_time + + l.text = "{\\fad(%d,%d)}%s" % ( + line.leadin/2, line.leadout/2, line.text_stripped) + + io.write_line(l) + +for li, line in enumerate(lines): + # Generating lines + if line.styleref.alignment >= 4: + romaji_kanji(line, line.copy()) + elif line.styleref.alignment <= 3: + lyrics(line, line.copy()) + +io.save() \ No newline at end of file diff --git a/pyonfx/__init__.py b/pyonfx/__init__.py new file mode 100644 index 00000000..d2d83f0f --- /dev/null +++ b/pyonfx/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +"""An easy way to do KFX and complex typesetting based on subtitle format ASS (Advanced Substation Alpha).""" + +from .ass_utility import Meta, Style, Line, Word, Syllable, Char, Ass +from .font_utility import Font +from .convert import Convert +from .utils import Utils +from .settings import Settings + +__version__ = '0.1.0' diff --git a/pyonfx/ass_utility.py b/pyonfx/ass_utility.py new file mode 100644 index 00000000..ddf9afc3 --- /dev/null +++ b/pyonfx/ass_utility.py @@ -0,0 +1,1006 @@ +# -*- coding: utf-8 -*- +# PyonFX: An easy way to do KFX and complex typesetting based on subtitle format ASS (Advanced Substation Alpha). +# Copyright (C) 2019 Antonio Strippoli (CoffeeStraw/YellowFlash) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# PyonFX is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +import os +import sys +import time +import re +import copy +from .font_utility import Font +from .convert import Convert +from .settings import Settings + + +class Meta: + """Meta object contains informations about the Ass. + + More info about each of them can be found on http://docs.aegisub.org/manual/Styles + + Attributes: + wrap_style (int): Determines how line breaking is applied to the subtitle line + scaled_border_and_shadow (bool): Determines if it has to be used script resolution (*True*) or video resolution (*False*) to scale border and shadow + play_res_x (int): Video Width + play_res_y (int): Video Height + audio (str): Loaded audio path (absolute) + video (str): Loaded video path (absolute) + """ + wrap_style = 0 + scaled_border_and_shadow = True + play_res_x = 0 + play_res_y = 0 + audio = "" + video = "" + + def __repr__(self): + return f"Meta Object {id(self)}:\n\tWrap Style: {self.wrap_style}\n\tScaled Border And Shadow: {self.scaled_border_and_shadow}\n\t"\ + f"Play Resolution X: {self.play_res_x}\n\tPlay Resolution Y: {self.play_res_y}\n\t"\ + f"Audio: {self.audio}\n\tVideo: {self.video}" + + +class Style: + """Style object contains a set of typographic formatting rules that is applied to dialogue lines. + + More info about styles can be found on http://docs.aegisub.org/3.2/ASS_Tags/. + + Attributes: + fontname (str): Font name + fontsize (int): Font size in points + color1 (str): Primary color (fill) + alpha1 (str): Trasparency of color1 + color2 (str): Secondary color (secondary fill, for karaoke effect) + alpha2 (str): Trasparency of color2 + color3 (str): Outline (border) color + alpha3 (str): Trasparency of color3 + color4 (str): Shadow color + alpha4 (str): Trasparency of color4 + bold (bool): Font with bold + italic (bool): Font with italic + underline (bool): Font with underline + strikeout (bool): Font with strikeout + scale_x (float): Text stretching in the horizontal direction + scale_y (float): Text stretching in the vertical direction + spacing (float): Horizontal spacing between letters + angle (float): Rotation of the text + border_style (bool): *True* for opaque box, *False* for standard outline + outline (float): Border thickness value + shadow (float): How far downwards and to the right a shadow is drawn + alignment (int): Alignment of the text + margin_l (int): Distance from the left of the video frame + margin_r (int): Distance from the right of the video frame + margin_v (int): Distance from the bottom (or top if alignment >= 7) of the video frame + encoding (int): Codepage used to map codepoints to glyphs + """ + fontname = "" + fontsize = 0 + color1 = "" + alpha1 = "" + color2 = "" + alpha2 = "" + color3 = "" + alpha3 = "" + color4 = "" + alpha4 = "" + bold = False + italic = False + underline = False + strikeout = False + scale_x = 100.0 + scale_y = 100.0 + spacing = 0.0 + angle = 0.0 + border_style = False + outline = 2.0 + shadow = 0.0 + alignment = 8 + margin_l = 30 + margin_r = 30 + margin_v = 30 + encoding = 1 + + def __repr__(self): + return str(self.__dict__) + + +class Line: + """Line object contains informations about a single line in the Ass. + + Note: + (*) = This field is available only if :class:`extended` = True + + Attributes: + comment (bool): If *True*, this line will not be displayed on the screen. + layer (int): Layer for the line. Higher layer numbers are drawn on top of lower ones. + start_time (int): Line start time (in milliseconds). + end_time (int): Line end time (in milliseconds). + duration (int): Line duration (in milliseconds) (*). + leadin (float): Time between this line and the previous one (in milliseconds; first line = 1000.1) (*). + leadout (float): Time between this line and the next one (in milliseconds; first line = 1000.1) (*). + style (str): Style name used for this line. + styleref (obj): Reference to the Style object of this line (*). + actor (str): Actor field. + margin_l (int): Left margin for this line. + margin_r (int): Right margin for this line. + margin_v (int): Vertical margin for this line. + effect (str): Effect field. + text (str): Line raw text. + text_stripped (str): Line stripped text. + width (float): Line text width (*). + height (float): Line text height (*). + ascent (float): Line font ascent (*). + descent (float): Line font descent (*). + internal_leading (float): Line font internal lead (*). + external_leading (float): Line font external lead (*). + x (float): Line text position horizontal (depends on alignment) (*). + y (float): Line text position vertical (depends on alignment) (*). + left (float): Line text position left (*). + center (float): Line text position center (*). + right (float): Line text position right (*). + top (float): Line text position top (*). + middle (float): Line text position middle (*). + bottom (float): Line text position bottom (*). + words (list): List containing objects :class:`Word` in this line (*). + syls (list): List containing objects :class:`Syllable` in this line (if available) (*). + chars (list): List containing objects :class:`Char` in this line (*). + """ + comment = False + layer = 0 + start_time = 0 + end_time = 0 + style = "" + actor = "" + margin_l = 0 + margin_r = 0 + margin_v = 0 + effect = "" + text = "" + text_stripped = "" + + def __repr__(self): + return str(self.__dict__) + + def copy(self): + """ + Returns: + A deep copy of this object (line) + """ + return copy.deepcopy(self) + + +class Word: + """Word object contains informations about a single word of a line in the Ass. + + A word can be defined as some text with some optional space before or after + (e.g.: In the string "What a beautiful world!", "beautiful" and "world" are both distinct words). + + Attributes: + start_time (int): Word start time (same as line start time) (in milliseconds). + end_time (int): Word end time (same as line end time) (in milliseconds). + duration (int): Word duration (same as line duration) (in milliseconds). + text (str): Word text. + prespace (int): Word free space before text. + postspace (int): Word free space after text. + width (float): Word text width. + height (float): Word text height. + ascent (float): Word font ascent. + descent (float): Word font descent. + internal_leading (float): Word font internal lead. + external_leading (float): Word font external lead. + x (float): Word text position horizontal (depends on alignment). + y (float): Word text position vertical (depends on alignment). + left (float): Word text position left. + center (float): Word text position center. + right (float): Word text position right. + top (float): Word text position top. + middle (float): Word text position middle. + bottom (float): Word text position bottom. + """ + start_time = 0 + end_time = 0 + duration = 0 + + text = "" + + prespace = 0 + postspace = 0 + + width = 0 + height = 0 + + ascent = 0 + descent = 0 + + internal_leading = 0 + external_leading = 0 + + x = 0 + y = 0 + + left = 0 + center = 0 + right = 0 + + top = 0 + middle = 0 + bottom = 0 + + def __repr__(self): + return str(self.__dict__) + + +class Syllable: + """Syllable object contains informations about a single syl of a line in the Ass. + + A syl can be defined as some text after a karaoke tag (k, ko, kf) + (e.g.: In "{\\k0}Hel{\\k0}lo {\\k0}Pyon{\\k0}FX {\\k0}users!", "Pyon" and "FX" are distinct syllables), + + Attributes: + word_i (int): Syllable word index (e.g.: In line text "{\\k0}Hel{\\k0}lo {\\k0}Pyon{\\k0}FX {\\k0}users!", syl "Pyon" will have word_i=1). + start_time (int): Syllable start time (in milliseconds). + end_time (int): Syllable end time (in milliseconds). + duration (int): Syllable duration (in milliseconds). + text (str): Syllable text. + inline_fx (str): Syllable inline effect (marked as \\-EFFECT in karaoke-time). + prespace (int): Syllable free space before text. + postspace (int): Syllable free space after text. + width (float): Syllable text width. + height (float): Syllable text height. + ascent (float): Syllable font ascent. + descent (float): Syllable font descent. + internal_leading (float): Syllable font internal lead. + external_leading (float): Syllable font external lead. + x (float): Syllable text position horizontal (depends on alignment). + y (float): Syllable text position vertical (depends on alignment). + left (float): Syllable text position left. + center (float): Syllable text position center. + right (float): Syllable text position right. + top (float): Syllable text position top. + middle (float): Syllable text position middle. + bottom (float): Syllable text position bottom. + """ + word_i = 0 + + start_time = 0 + end_time = 0 + duration = 0 + + text = "" + inline_fx = "" + + prespace = 0 + postspace = 0 + + width = 0 + height = 0 + + ascent = 0 + descent = 0 + + internal_leading = 0 + external_leading = 0 + + x = 0 + y = 0 + + left = 0 + center = 0 + right = 0 + + top = 0 + middle = 0 + bottom = 0 + + def __repr__(self): + return str(self.__dict__) + + +class Char: + """Char object contains informations about a single char of a line in the Ass. + + A char is defined by some text between two karaoke tags (k, ko, kf). + + Attributes: + word_i (int): Char word index (e.g.: In line text "Hello PyonFX users!", letter "u" will have word_i=2). + syl_i (int): Char syl index (e.g.: In line text "{\\k0}Hel{\\k0}lo {\\k0}Pyon{\\k0}FX {\\k0}users!", letter "F" will have syl_i=3). + syl_char_i (int): Char invidual syl index (e.g.: In line text "{\\k0}Hel{\\k0}lo {\\k0}Pyon{\\k0}FX {\\k0}users!", letter "e" of "users" will have syl_char_i=2). + start_time (int): Char start time (in milliseconds). + end_time (int): Char end time (in milliseconds). + duration (int): Char duration (in milliseconds). + text (str): Char text. + inline_fx (str): Char inline effect (marked as \\-EFFECT in karaoke-time). + prespace (int): Char free space before text. + postspace (int): Char free space after text. + width (float): Char text width. + height (float): Char text height. + ascent (float): Char font ascent. + descent (float): Char font descent. + internal_leading (float): Char font internal lead. + external_leading (float): Char font external lead. + x (float): Char text position horizontal (depends on alignment). + y (float): Char text position vertical (depends on alignment). + left (float): Char text position left. + center (float): Char text position center. + right (float): Char text position right. + top (float): Char text position top. + middle (float): Char text position middle. + bottom (float): Char text position bottom. + """ + word_i = -1 + syl_i = -1 + syl_char_i = -1 + + start_time = 0 + end_time = 0 + duration = 0 + + text = "" + + width = 0 + height = 0 + + ascent = 0 + descent = 0 + + internal_leading = 0 + external_leading = 0 + + x = 0 + y = 0 + + left = 0 + center = 0 + right = 0 + + top = 0 + middle = 0 + bottom = 0 + + def __repr__(self): + return str(self.__dict__) + + +class Ass: + """Contains all the informations about a file in the ASS format and the methods to work with it. + + Usually you will create an Ass object and use it for input and output (see example_ section). + PyonFX set automatically an absolute path for all the info in the output, so that wherever you will + put your generated file, it will always take the right path for the video and the audio. + + Args: + path_input (str): Path for the input file (either relative or absolute). + path_output (str): Path for the output file (either relative or absolute) (DEFAULT: "Output.ass"). + extended (bool): Calculate more informations from lines (usually you will not have to touch this). + vertical_kanji (bool): If True, line text with alignment 4, 5 or 6 will be positioned vertically. + + Attributes: + path_input (str): Path for input file (absolute). + path_output (str): Path for output file (absolute). + meta (:class:`Meta`): Contains informations about the ASS given. + styles (list of :class:`Style`): Contains all the styles in the ASS given. + lines (list of :class:`Line`): Contains all the lines (events) in the ASS given. + + .. _example: + Example: + >>> io = Ass("in.ass") + >>> meta, styles, lines = io.get_data() + + """ + def __init__(self, path_input="", path_output="Output.ass", extended=True, vertical_kanji=True): + # Starting to take process time + self.__plines = 0 + self.__ptime = time.time() + + self.meta, self.styles, self.lines = Meta(), {}, [] + # Getting absolute sub file path + dirname = os.path.dirname(os.path.abspath(sys.argv[0])) + if not os.path.isabs(path_input): + path_input = os.path.join(dirname, path_input) + + # Getting absolute output file path + if path_output == "Output.ass": + path_output = os.path.join(dirname, path_output) + elif not os.path.isabs(path_output): + path_output = os.path.join(dirname, path_output) + + self.path_input = path_input + self.path_output = path_output + + # Checking sub file validity (does it exists?) + if not os.path.isfile(path_input): + raise FileNotFoundError("Invalid path for the Subtitle file: %s" % path_input) + + self.meta.sub = path_input + section = "" + self.__output = [] + for line in open(self.meta.sub, "r", encoding="utf-8-sig"): + # Getting section + section_pattern = re.compile(r"^\[([^\]]*)") + if section_pattern.match(line): + # Updating section + section = section_pattern.match(line).group(1) + # Appending line to output + self.__output.append(line) + + # Parsing Meta data + elif section == "Script Info" or section == "Aegisub Project Garbage": + # Internal function that tries to get the absolute path for media files in meta + def get_media_abs_path(subfile, mediafile): + if not os.path.isfile(mediafile): + tmp = mediafile + media_dir = os.path.dirname(subfile) + while mediafile.startswith("../"): + media_dir = os.path.dirname(media_dir) + mediafile = mediafile[3:] + + mediafile = os.path.normpath("%s%s%s" % (media_dir, os.sep, mediafile)) + if not os.path.isfile(mediafile): + mediafile = tmp + return mediafile + + # Switch + if re.match(r"^WrapStyle: *?(\d+)$", line): + self.meta.wrap_style = int(line[11:].strip()) + elif re.match(r"^ScaledBorderAndShadow: *?(.+)$", line): + self.meta.scaled_border_and_shadow = line[23:].strip() == "yes" + elif re.match(r"^PlayResX: *?(\d+)$", line): + self.meta.play_res_x = int(line[10:].strip()) + elif re.match(r"^PlayResY: *?(\d+)$", line): + self.meta.play_res_y = int(line[10:].strip()) + elif re.match(r"^Audio File: *?(.*)$", line): + self.meta.audio = get_media_abs_path(self.meta.sub, line[11:].strip()) + line = "Audio File: %s\n" % self.meta.audio + elif re.match(r"^Video File: *?(.*)$", line): + self.meta.video = get_media_abs_path(self.meta.sub, line[11:].strip()) + line = "Video File: %s\n" % self.meta.video + + # Appending line to output + self.__output.append(line) + # Parsing Styles + elif section == "V4+ Styles": + # Appending line to output + self.__output.append(line) + style = re.match(r"^Style: (.+?)$", line) + + if style: + # Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, + # Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, + # BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding + style = [el for el in style.group(1).split(',')] + tmp = Style() + + tmp.fontname = style[1] + tmp.fontsize = float(style[2]) + + r, g, b, a = Convert.coloralpha(style[3]) + tmp.color1 = Convert.coloralpha(r, g, b) + tmp.alpha1 = Convert.coloralpha(a) + + r, g, b, a = Convert.coloralpha(style[4]) + tmp.color2 = Convert.coloralpha(r, g, b) + tmp.alpha2 = Convert.coloralpha(a) + + r, g, b, a = Convert.coloralpha(style[5]) + tmp.color3 = Convert.coloralpha(r, g, b) + tmp.alpha3 = Convert.coloralpha(a) + + r, g, b, a = Convert.coloralpha(style[6]) + tmp.color4 = Convert.coloralpha(r, g, b) + tmp.alpha4 = Convert.coloralpha(a) + + tmp.bold = style[7] == "-1" + tmp.italic = style[8] == "-1" + tmp.underline = style[9] == "-1" + tmp.strikeout = style[10] == "-1" + + tmp.scale_x = float(style[11]) + tmp.scale_y = float(style[12]) + + tmp.spacing = float(style[13]) + tmp.angle = float(style[14]) + + tmp.border_style = style[15] == "3" + tmp.outline = float(style[16]) + tmp.shadow = float(style[17]) + + tmp.alignment = int(style[18]) + tmp.margin_l = float(style[19]) + tmp.margin_r = float(style[20]) + tmp.margin_v = float(style[21]) + + tmp.encoding = int(style[22]) + + self.styles[style[0]] = tmp + # Parsing Dialogues + elif section == "Events": + # Appending line to output (commented) + self.__output.append(re.sub(r"^(Dialogue|Comment):", "Comment:", line)) + + # Analyzing line + line = re.match(r"^(Dialogue|Comment): (.+?)$", line) + + if line: + # Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text + tmp = Line() + tmp.comment = line.group(1) == "Comment" + line = [el for el in line.group(2).split(',')] + + tmp.layer = int(line[0]) + + tmp.start_time = Convert.time(line[1]) + tmp.end_time = Convert.time(line[2]) + + tmp.style = line[3] + tmp.actor = line[4] + + tmp.margin_l = int(line[5]) + tmp.margin_r = int(line[6]) + tmp.margin_v = int(line[7]) + + tmp.effect = line[8] + + tmp.text = ','.join(line[9:]) + + self.lines.append(tmp) + + + # Adding informations to lines and meta? + if extended: + lines_by_styles = {} + # Let the fun begin (Pyon!) + for li, line in enumerate(self.lines): + try: + line.styleref = self.styles[line.style] + except KeyError: + line.styleref = None + + # Append dialog to styles (for leadin and leadout later) + if line.style not in lines_by_styles: + lines_by_styles[line.style] = [] + lines_by_styles[line.style].append(line) + + line.duration = line.end_time - line.start_time + line.text_stripped = re.sub(r"\{.*?\}", "", line.text) + + # Add dialog text sizes and positions (if possible) + if line.styleref: + font = Font(line.styleref) + line.width, line.height = font.get_text_extents(line.text_stripped) + line.ascent, line.descent, line.internal_leading, line.external_leading = font.get_metrics() + if self.meta.play_res_x > 0 and self.meta.play_res_y > 0: + # Horizontal position + if (line.styleref.alignment-1) % 3 == 0: + line.left = line.margin_l if line.margin_l != 0 else line.styleref.margin_l + line.center = line.left + line.width / 2 + line.right = line.left + line.width + line.x = line.left + elif (line.styleref.alignment-2) % 3 == 0: + line.left = self.meta.play_res_x / 2 - line.width / 2 + line.center = line.left + line.width / 2 + line.right = line.left + line.width + line.x = line.center + else: + line.left = self.meta.play_res_x - (line.margin_r if line.margin_r != 0 else line.styleref.margin_r) - line.width + line.center = line.left + line.width / 2 + line.right = line.left + line.width + line.x = line.right + + # Vertical position + if line.styleref.alignment > 6: + line.top = line.margin_v if line.margin_v != 0 else line.styleref.margin_v + line.middle = line.top + line.height / 2 + line.bottom = line.top + line.height + line.y = line.top + elif line.styleref.alignment > 3: + line.top = self.meta.play_res_y / 2 - line.height / 2 + line.middle = line.top + line.height / 2 + line.bottom = line.top + line.height + line.y = line.middle + else: + line.top = self.meta.play_res_y - (line.margin_v if line.margin_v != 0 else line.styleref.margin_v) - line.height + line.middle = line.top + line.height / 2 + line.bottom = line.top + line.height + line.y = line.bottom + + # Calculating space width + space_width = font.get_text_extents(" ")[0] + + # Adding words + line.words = [] + + for prespace, word_text, postspace in re.findall(r"(\s*)(\w+)(\s*)", line.text_stripped): + word = Word() + + word.start_time = line.start_time + word.end_time = line.end_time + word.duration = line.duration + + word.text = word_text + + word.prespace = len(prespace) + word.postspace = len(postspace) + + word.width, word.height = font.get_text_extents(word.text) + word.ascent, word.descent, word.internal_leading, word.external_leading = font.get_metrics() + + line.words.append(word) + + # Calculate word positions with all words data already available + if len(line.words) > 0 and self.meta.play_res_x > 0 and self.meta.play_res_y > 0: + if line.styleref.alignment > 6 or line.styleref.alignment < 4: + cur_x = line.left + for word in line.words: + # Horizontal position + cur_x = cur_x + word.prespace * space_width + + word.left = cur_x + word.center = word.left + word.width / 2 + word.right = word.left + word.width + + if (line.styleref.alignment-1) % 3 == 0: + word.x = word.left + elif (line.styleref.alignment-2) % 3 == 0: + word.x = word.center + else: + word.x = word.right + + # Vertical position + word.top = line.top + word.middle = line.middle + word.bottom = line.bottom + word.y = line.y + + # Updating cur_x + cur_x = cur_x + word.width + word.postspace * space_width + else: + max_width, sum_height = 0, 0 + for word in line.words: + max_width = max(max_width, word.width) + sum_height = sum_height + word.height + + cur_y = x_fix = self.meta.play_res_y / 2 - sum_height / 2 + for word in line.words: + # Horizontal position + x_fix = (max_width - word.width) / 2 + + if line.styleref.alignment == 4: + word.left = line.left + x_fix + word.center = word.left + word.width / 2 + word.right = word.left + word.width + word.x = word.left + elif line.styleref.alignment == 5: + word.left = self.meta.play_res_x / 2 - word.width / 2 + word.center = word.left + word.width / 2 + word.right = word.left + word.width + word.x = word.center + else: + word.left = line.right - word.width - x_fix + word.center = word.left + word.width / 2 + word.right = word.left + word.width + word.x = word.right + + # Vertical position + word.top = cur_y + word.middle = word.top + word.height / 2 + word.bottom = word.top + word.height + word.y = word.middle + cur_y = cur_y + word.height + + + # Add dialog text chunks, to create syllables + text_chunks = [] + tag_pattern = re.compile(r"\{.*?\}") + tag = tag_pattern.search(line.text) + word_i = 0 + + if not tag: + # No tags found + text_chunks.append({'tags': "", 'text': line.text}) + else: + # First chunk without tags + if tag.start() != 0: + text_chunks.append({'tags': "", 'text': line.text[0:tag.start()]}) + + # Searching for other tags + while True: + next_tag = tag_pattern.search(line.text, tag.end()) + tmp = {'tags': line.text[tag.start()+1:tag.end()-1], 'text': line.text[tag.end():(next_tag.start() if next_tag else None)], 'word_i': word_i} + text_chunks.append(tmp) + + if len(re.findall(r"(.*?)(\s*)$", tmp['text'])[0][1]) > 0: + word_i = word_i + 1 + + if not next_tag: + break + tag = next_tag + + # Adding syls + last_time = 0 + line.syls = [] + for text_chunk in text_chunks: + try: + pretags, kdur, posttags = re.findall(r"(.*?)\\[kK][of]?(\d+)(.*?)", text_chunk['tags'])[0][:] + syl = Syllable() + + syl.word_i = text_chunk['word_i'] + + syl.start_time = last_time + syl.end_time = last_time + int(kdur) * 10 + syl.duration = int(kdur) * 10 + + syl.inline_fx = "" + syl.tags = pretags + posttags + syl.prespace, syl.text, syl.postspace = re.findall(r"^(\s*)(.*?)(\s*)$", text_chunk['text'])[0][:] + + syl.prespace, syl.postspace = len(syl.prespace), len(syl.postspace) + syl.width, syl.height = font.get_text_extents(syl.text) + syl.ascent, syl.descent, syl.internal_leading, syl.external_leading = font.get_metrics() + + line.syls.append(syl) + last_time = syl.end_time + except IndexError: + line.syls.clear() + break + + # Calculate syllables positions with all syllables data already available + if len(line.syls) > 0 and self.meta.play_res_x > 0 and self.meta.play_res_y > 0: + if line.styleref.alignment > 6 or line.styleref.alignment < 4 or not vertical_kanji: + cur_x = line.left + for syl in line.syls: + cur_x = cur_x + syl.prespace * space_width + # Horizontal position + syl.left = cur_x + syl.center = syl.left + syl.width / 2 + syl.right = syl.left + syl.width + + if (line.styleref.alignment-1) % 3 == 0: + syl.x = syl.left + elif (line.styleref.alignment-2) % 3 == 0: + syl.x = syl.center + else: + syl.x = syl.right + + cur_x = cur_x + syl.width + syl.postspace * space_width + + # Vertical position + syl.top = line.top + syl.middle = line.middle + syl.bottom = line.bottom + syl.y = line.y + + else: # Kanji vertical position + max_width, sum_height = 0, 0 + for syl in line.syls: + max_width = max(max_width, syl.width) + sum_height = sum_height + syl.height + + cur_y = self.meta.play_res_y / 2 - sum_height / 2 + + # Fixing line positions + line.top = cur_y + line.middle = self.meta.play_res_y / 2 + line.bottom = line.top + sum_height + line.width = max_width + line.height = sum_height + if line.styleref.alignment == 4: + line.center = line.left + max_width / 2 + line.right = line.left + max_width + elif line.styleref.alignment == 5: + line.left = line.center - max_width / 2 + line.right = line.left + max_width + else: + line.left = line.right - max_width + line.center = line.left + max_width / 2 + + for syl in line.syls: + # Horizontal position + x_fix = (max_width - syl.width) / 2 + if line.styleref.alignment == 4: + syl.left = line.left + x_fix + syl.center = syl.left + syl.width / 2 + syl.right = syl.left + syl.width + syl.x = syl.left + elif line.styleref.alignment == 5: + syl.left = line.center - syl.width / 2 + syl.center = syl.left + syl.width / 2 + syl.right = syl.left + syl.width + syl.x = syl.center + else: + syl.left = line.right - syl.width - x_fix + syl.center = syl.left + syl.width / 2 + syl.right = syl.left + syl.width + syl.x = syl.right + + # Vertical position + syl.top = cur_y + syl.middle = syl.top + syl.height / 2 + syl.bottom = syl.top + syl.height + syl.y = syl.middle + cur_y = cur_y + syl.height + + # Adding chars + line.chars = [] + + # Creating some local variables to avoid some useless iterations during the additions of some fields in char obj + word_index = 0 + syl_index = 0 + char_index = 0 + + tmp = "" + if line.words: + tmp = line.words[0] + if line.syls: + tmp = line.syls[0] + + # Getting chars + for char_i, char_text in enumerate(list(line.text_stripped)): + char = Char() + + char.start_time = line.start_time + char.end_time = line.end_time + char.duration = line.duration + + char.text = char_text + + # Adding indexes + if line.syls: + if char_index >= len("{}{}{}".format(" "*tmp.prespace, tmp.text, " "*tmp.postspace)): + char_index = 0 + syl_index += 1 + tmp = line.syls[syl_index] + + char.word_i = tmp.word_i + char.syl_i = syl_index + char.syl_char_i = char_index + else: # We have no syls, let's only work with words + if char_index >= len("{}{}{}".format(" "*tmp.prespace, tmp.text, " "*tmp.postspace)): + char_index = 0 + word_index += 1 + tmp = line.words[syl_index] + + char.word_i = word_index + + # Adding last fields based on the existance of syls or not + char.start_time = tmp.start_time + char.end_time = tmp.end_time + char.duration = tmp.duration + + char.width, char.height = font.get_text_extents(char.text) + char.ascent, char.descent, char.internal_leading, char.external_leading = font.get_metrics() + + line.chars.append(char) + char_index += 1 + + # Calculate character positions with all characters data already available + if len(line.chars) > 0 and self.meta.play_res_x > 0 and self.meta.play_res_y > 0: + if line.styleref.alignment > 6 or line.styleref.alignment < 4: + cur_x = line.left + for char in line.chars: + # Horizontal position + char.left = cur_x + char.center = char.left + char.width / 2 + char.right = char.left + char.width + + if (line.styleref.alignment-1) % 3 == 0: + char.x = char.left + elif (line.styleref.alignment-2) % 3 == 0: + char.x = char.center + else: + char.x = char.right + + cur_x = cur_x + char.width + + # Vertical position + char.top = line.top + char.middle = line.middle + char.bottom = line.bottom + char.y = line.y + else: + max_width, sum_height = 0, 0 + for char in line.chars: + max_width = max(max_width, char.width) + sum_height = sum_height + char.height + + cur_y = x_fix = self.meta.play_res_y / 2 - sum_height / 2 + for char in line.chars: + # Horizontal position + x_fix = (max_width - char.width) / 2 + if line.styleref.alignment == 4: + char.left = line.left + x_fix + char.center = char.left + char.width / 2 + char.right = char.left + char.width + char.x = char.left + elif line.styleref.alignment == 5: + char.left = self.meta.play_res_x / 2 - char.width / 2 + char.center = char.left + char.width / 2 + char.right = char.left + char.width + char.x = char.center + else: + char.left = line.right - char.width - x_fix + char.center = char.left + char.width / 2 + char.right = char.left + char.width + char.x = char.right + + # Vertical position + char.top = cur_y + char.middle = char.top + char.height / 2 + char.bottom = char.top + char.height + char.y = char.middle + cur_y = cur_y + char.height + + # Add durations between dialogs + for style in lines_by_styles: + lines_by_styles[style].sort(key=lambda x: x.start_time) + for li, line in enumerate(lines_by_styles[style]): + line.leadin = 1000.1 if li == 0 else line.start_time - lines_by_styles[style][li-1].end_time + line.leadout = 1000.1 if li == len(lines_by_styles[style])-1 else lines_by_styles[style][li+1].start_time - line.end_time + + # Done + + def get_data(self): + """Utility function to retrieve easily meta styles and lines. + + Returns: + :attr:`meta`, :attr:`styles` and :attr:`lines` + """ + return self.meta, self.styles, self.lines + + def write_line(self, line): + """Appends a line to the output list (which is private). + + Use it whenever you've prepared a line, it will not impact performance since you + will not actually write anything until :func:`save` will be called. + + Parameters: + line (:class:`Line`): A line object. If not valid, TypeError is raised. + """ + if isinstance(line, Line): + self.__output.append("\n%s: %d,%s,%s,%s,%s,%04d,%04d,%04d,%s,%s" % ( + "Comment" if line.comment else "Dialogue", + line.layer, + Convert.time(int(line.start_time)), + Convert.time(int(line.end_time)), + line.style, + line.actor, + line.margin_l, + line.margin_r, + line.margin_v, + line.effect, + line.text + )) + self.__plines += 1 + else: + raise TypeError("Expected Line object, got %s." % type(line)) + + def save(self, quiet=False): + """Write everything inside the output list to a file. + + This should be the last function called inside your fx.py file. + Additionally, if pyonfx.Settings.aegisub is True, then the file will automatically + be opened with Aegisub at the end of the generation. + + Parameters: + quiet (bool): If True, you will not get printed any message. + """ + + # Writing to file + with open(self.path_output, 'w', encoding="utf-8-sig") as f: + f.writelines(self.__output) + if not quiet: + print("Produced lines: %d\nProcess duration (in seconds): %.3f" % (self.__plines, time.time() - self.__ptime)) + + # Open with Aegisub? + if Settings.aegisub: + os.startfile(self.path_output) diff --git a/pyonfx/convert.py b/pyonfx/convert.py new file mode 100644 index 00000000..aae06441 --- /dev/null +++ b/pyonfx/convert.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +# PyonFX: An easy way to do KFX and complex typesetting based on subtitle format ASS (Advanced Substation Alpha). +# Copyright (C) 2019 Antonio Strippoli (CoffeeStraw/YellowFlash) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# PyonFX is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +import re +import math + +class Convert: + """ + This class is a collection of static methods that will help + the user toconvert everything needed to the ASS format. + """ + + @staticmethod + def time(ass_ms): + """Converts between milliseconds and ASS timestamp. + + Parameters: + ass_ms (either int or str): If int, than milliseconds are expected, else ASS timestamp as str is expected. + + Returns: + If milliseconds -> ASS timestamp, else if ASS timestamp -> milliseconds, else ValueError will be raised. + """ + # Milliseconds? + if type(ass_ms) is int and ass_ms >= 0: + return "{:d}:{:02d}:{:02d}.{:02d}".format( + math.floor(ass_ms / 3600000) % 10, + math.floor(ass_ms % 3600000 / 60000), + math.floor(ass_ms % 60000 / 1000), + math.floor(ass_ms % 1000 / 10)) + # ASS timestamp? + elif type(ass_ms) is str and re.match(r"^\d:\d+:\d+\.\d+$", ass_ms): + return int(ass_ms[0]) * 3600000 + int(ass_ms[2:4]) * 60000 + int(ass_ms[5:7]) * 1000 + int(ass_ms[8:10]) * 10 + else: + raise ValueError("Milliseconds or ASS timestamp expected") + + @staticmethod + def coloralpha(ass_r_a, g="", b="", a=""): + """Converts between rgb color &/+ alpha numeric and ASS color &/+ alpha. + + Parameters: + ass_r_a (int or str): If str, an ASS color + optionally alpha or only alpha is expected, else if it is a number this will be the red value or the alpha value in rgb. + g (int, optional): If given, an rgb + optional alpha is expected, so you will have to fill also the other parameters. + b (int, optional): If given, an rgb + optional alpha is expected, so you will have to fill also the other parameters. + a (int, optional): If given, an rgb + alpha is expected, so you will have to fill also the other parameters. + + Returns: + According to the parameters, either an rgb + optionally alpha as multiple returns value or a str containing either an ASS color+alpha, an ASS color or an ASS alpha. + + """ + # Alpha / red numeric? + if type(ass_r_a) == int and ass_r_a >= 0 and ass_r_a <= 255: + # Green + blue numeric? + if type(g) == int and g >= 0 and g <= 255 and type(b) == int and b >= 0 and b <= 255: + # Alpha numeric? + if type(a) == int and a >= 0 and a <= 255: + return "&H{:02X}{:02X}{:02X}{:02X}".format(255 - a, b, g, ass_r_a) + else: + return "&H{:02X}{:02X}{:02X}&".format(b, g, ass_r_a) + else: + return "&H{:02X}&".format(255 - ass_r_a) + # ASS value? + elif type(ass_r_a) == str: + # ASS alpha? + if re.match(r"^&H[0-9a-fA-F]{2}&$", ass_r_a): + return 255 - int(ass_r_a[2:4], 16) + # ASS color? + elif re.match(r"^&H[0-9a-fA-F]{6}&$", ass_r_a): + return int(ass_r_a[6:8], 16), int(ass_r_a[4:6], 16), int(ass_r_a[2:4], 16) + # ASS color+alpha (from style definition)? + elif re.match(r"^&H[0-9a-fA-F]{8}$", ass_r_a): + return int(ass_r_a[8:10], 16), int(ass_r_a[6:8], 16), int(ass_r_a[4:6], 16), 255 - int(ass_r_a[2:4], 16) + else: + raise ValueError("Invalid ASS string") + else: + raise ValueError("Color, Alpha, Color+Alpha as numeric or ASS expected") + + @staticmethod + def shape_to_pixels(shape): + pass + + @staticmethod + def text_to_shape(text, style): + pass + + @staticmethod + def text_to_pixels(text, style, off_x=0, off_y=0): + pass + + @staticmethod + def image_to_ass(image): + pass + + @staticmethod + def image_to_pixels(image): + pass diff --git a/pyonfx/font_utility.py b/pyonfx/font_utility.py new file mode 100644 index 00000000..34851fb2 --- /dev/null +++ b/pyonfx/font_utility.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# PyonFX: An easy way to do KFX and complex typesetting based on subtitle format ASS (Advanced Substation Alpha). +# Copyright (C) 2019 Antonio Strippoli (CoffeeStraw/YellowFlash) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# PyonFX is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +""" +This file contains the Font class definition, which has some functions +to help getting informations from a specific font +""" +import sys +import win32gui +import win32ui +import win32con + +# CONFIGURATION +FONT_PRECISION = 64 # Font scale for better precision output from native font system + +class Font: + """ + Font class definition + """ + def __init__(self, style): + self.family = style.fontname + self.bold = style.bold + self.italic = style.italic + self.underline = style.underline + self.strikeout = style.strikeout + self.size = style.fontsize + self.xscale = style.scale_x / 100 + self.yscale = style.scale_y / 100 + self.hspace = style.spacing + self.upscale = FONT_PRECISION + self.downscale = 1 / FONT_PRECISION + + if sys.platform == "win32": + # Create device context + self.dc = win32gui.CreateCompatibleDC(None) + # Set context coordinates mapping mode + win32gui.SetMapMode(self.dc, win32con.MM_TEXT) + # Set context backgrounds to transparent + win32gui.SetBkMode(self.dc, win32con.TRANSPARENT) + # Create font handle + font_spec = { + 'height': int(self.size * self.upscale), + 'width': 0, + 'escapement': 0, + 'orientation': 0, + 'weight': win32con.FW_BOLD if self.bold else win32con.FW_NORMAL, + 'italic': int(self.italic), + 'underline': int(self.underline), + 'strike out': int(self.strikeout), + 'charset': win32con.DEFAULT_CHARSET, + 'out precision': win32con.OUT_TT_PRECIS, + 'clip precision': win32con.CLIP_DEFAULT_PRECIS, + 'quality': win32con.ANTIALIASED_QUALITY, + 'pitch and family': win32con.DEFAULT_PITCH + win32con.FF_DONTCARE, + 'name': self.family + } + font = win32ui.CreateFont(font_spec) + win32gui.SelectObject(self.dc, font.GetSafeHandle()) + else: + raise NotImplementedError + + def get_metrics(self): + metrics = win32gui.GetTextMetrics(self.dc) + + return ( + #'height': metrics['Height'] * self.downscale * self.yscale, + metrics['Ascent'] * self.downscale * self.yscale, + metrics['Descent'] * self.downscale * self.yscale, + metrics['InternalLeading'] * self.downscale * self.yscale, + metrics['ExternalLeading'] * self.downscale * self.yscale + ) + + def get_text_extents(self, text): + cx, cy = win32gui.GetTextExtentPoint32(self.dc, text) + + return ( + (cx * self.downscale + self.hspace) * self.xscale, + cy * self.downscale * self.yscale + ) diff --git a/pyonfx/settings.py b/pyonfx/settings.py new file mode 100644 index 00000000..add083b8 --- /dev/null +++ b/pyonfx/settings.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +class Settings: + """ + Class Settings with the following fields: + """ + + aegisub = True + """ + If **True**, PyonFX will automatically open the output with Aegisub at the end of the generation. + """ + + mpv = False + """ + | **Currently Not Implemented**. + | If **True**, PyonFX will automatically open the output with mpv, playing in softsub. + """ \ No newline at end of file diff --git a/pyonfx/utils.py b/pyonfx/utils.py new file mode 100644 index 00000000..a8d97335 --- /dev/null +++ b/pyonfx/utils.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# PyonFX: An easy way to do KFX and complex typesetting based on subtitle format ASS (Advanced Substation Alpha). +# Copyright (C) 2019 Antonio Strippoli (CoffeeStraw/YellowFlash) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# PyonFX is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +class Utils: + """ + This class is a collection of static methods that will help the user in some tasks. + """ + + @staticmethod + def all_non_empty(lines_chars_syls_or_words): + """ + Helps to not check everytime for text containing only spaces or object's duration equals to zero. + + Parameters: + lines_chars_syls_or_words (list of :class:`Line`, :class:`Char`, :class:`Syllable` or :class:`Word`) + + Returns: + An enumerate object containing lines_chars_syls_or_words without objects with duration equals to zero or without some text except spaces. + """ + out = [] + for obj in lines_chars_syls_or_words: + if obj.text.strip() and obj.duration > 0: + out.append(obj) + return enumerate(out) + + def clean_tags(text): + # TODO: Cleans up ASS subtitle lines of badly-formed override. Returns a cleaned up text. + pass \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..97b2a943 --- /dev/null +++ b/setup.py @@ -0,0 +1,21 @@ +import setuptools +from pyonfx import __version__ + +setuptools.setup( + name="PyonFX", + url="https://github.com/CoffeeStraw/PyonFX", + author="Antonio Strippoli", + author_email="clarantonio98@gmail.com", + version=__version__, + license='GNU LGPL 3.0 or later', + description="An easy way to do KFX and complex typesetting based on subtitle format ASS (Advanced Substation Alpha).", + long_description=open('README.rst', 'rt').read(), + packages=['pyonfx'], + install_requires=[], + classifiers=[ + 'Development Status :: 2 - Pre-Alpha', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + ], +) \ No newline at end of file diff --git a/tests/Ass/in.ass b/tests/Ass/in.ass new file mode 100644 index 00000000..b67990c3 --- /dev/null +++ b/tests/Ass/in.ass @@ -0,0 +1,58 @@ +[Script Info] +; Script generated by Aegisub 8962-master-5c15667 +; http://www.aegisub.org/ +PlayResX: 1280 +PlayResY: 720 + +[Aegisub Project Garbage] +Last Style Storage: Default +Video File: ?dummy:23.976000:2250:1920:1080:11:135:226:c +Video AR Value: 1.777778 +Video Zoom Percent: 0.625000 +Active Line: 1 +Video Position: 342 + +[V4+ Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding +Style: Default,Arial,48,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,8,25,25,25,1 +Style: Romaji,Migu 1P,48,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,8,25,25,25,1 +Style: Translation,Migu 1P,46,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,25,25,25,1 +Style: Kanji,Migu 1P,44,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,4,25,25,25,1 + +[Events] +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text +Comment: 0,0:00:00.00,0:00:00.00,Default,,0,0,0,,Font used (Version 1P, Bold): https://www.freejapanesefont.com/migu-font-%E3%83%9F%E3%82%B0%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88/ +Dialogue: 0,0:00:14.24,0:00:24.23,Romaji,,0,0,0,,{\k56}su{\k13}re{\k22}chi{\k36}ga{\k48}u{\k25} {\k34}ko{\k33}to{\k50}ba {\k15}no {\k17}u{\k34}ra {\k46}ni{\k33} {\k28}to{\k36}za{\k65}sa{\k33}{\k30}re{\k51}ta{\k16} {\k33}ko{\k33}ko{\k78}ro {\k15}no {\k24}ka{\k95}gi +Dialogue: 0,0:00:24.68,0:00:34.50,Romaji,,0,0,0,,{\k51}ki{\k13}mi {\k18}to {\k32}i{\k49}u{\k28} {\k37}fu{\k32}ra{\k46}gu {\k19}ka{\k13}i{\k35}jo {\k51}ga{\k30} {\k33}e{\k34}ga{\k32}o{\k30} {\k35}su{\k38}ku{\k59}e{\k64}ru {\k67}no{\k29} {\k19}na{\k88}ra +Dialogue: 0,0:00:35.06,0:00:40.09,Romaji,,0,0,0,,{\k16}na{\k18}tsu {\k15}no {\k30}ka{\k35}ze {\k12}i{\k40}za{\k14}na{\k50}u{\k32} {\k17}ha{\k12}ku{\k19}chu{\k31}u {\k25}no {\k23}su{\k33}ko{\k15}o{\k66}ru +Dialogue: 0,0:00:40.84,0:00:44.80,Romaji,,0,0,0,,{\k34}ha{\k37}cho{\k39}u{\k16} {\k76}shi{\k96}n{\k12}ku{\k86}ro +Dialogue: 0,0:00:45.41,0:00:50.30,Romaji,,0,0,0,,{\k21}ro{\k27}man{\k36}chi{\k33}kku {\k16}mi{\k36}ta{\k19}i {\k40}ni{\k35} {\k17}ki {\k16}no {\k16}ki{\k30}i{\k33}ta {\k18}ko{\k31}to{\k19}ba {\k46}mo +Dialogue: 0,0:00:50.58,0:00:59.34,Romaji,,0,0,0,,{\k37}mi{\k17}tsu{\k47}ka{\k19}ra{\k46}nai {\k19}ke{\k36}do{\k45} {\k32}a{\k28}o{\k33}zo{\k36}ra {\k29}o{\k31} {\k32}me{\k31}za{\k178}su{\k56} {\k14}ka{\k110}ra +Dialogue: 0,0:00:59.70,0:01:04.24,Romaji,,0,0,0,,{\k33}bo{\k19}ku{\k30}ta{\k33}chi {\k85}wa{\k27} {\k15}hi{\k16}to{\k36}tsu {\k17}ni {\k31}na{\k32}re{\k80}ru +Dialogue: 0,0:01:04.56,0:01:09.78,Romaji,,0,0,0,,{\k22}to{\k16}ma{\k30}do{\k16}i {\k35}no {\k30}na{\k111}mi{\k20}da {\k43}mo{\k34} {\k34}yu{\k31}me {\k100}mo +Dialogue: 0,0:01:10.00,0:01:15.96,Romaji,,0,0,0,,{\k46}shi{\k11}n{\k29}ji{\k33}tsu {\k84}ni{\k26} {\k24}hi{\k10}ki{\k29}yo{\k27}se{\k31}ra{\k30}re{\k115}ru {\k17}mo{\k84}no +Dialogue: 0,0:01:16.20,0:01:24.48,Romaji,,0,0,0,,{\k33}so{\k55}re {\k26}wa{\k24} {\k13}i{\k9}to{\k48}shi{\k62}su{\k19}gi{\k85}ru{\k108} {\k40}ma{\k36}ho{\k63}u {\k32}no {\k18}ki{\k32}i{\k101}waa{\k24}do +Comment: 0,0:01:24.39,0:01:26.39,Default,,0,0,0,, +Dialogue: 0,0:00:14.24,0:00:18.56,Kanji,,0,0,0,,{\k56}す{\k13}れ{\k58}違{\k48}う{\k25}{\k67}言{\k50}葉{\k15}の{\k51}裏{\k49}に +Dialogue: 0,0:00:18.86,0:00:24.23,Kanji,,0,0,0,,{\k28}閉{\k36}ざ{\k65}さ{\k33}{\k30}れ{\k51}た{\k16}{\k144}心{\k15}の{\k24}カ{\k95}ギ +Dialogue: 0,0:00:24.68,0:00:28.91,Kanji,,0,0,0,,{\k51}キ{\k13}ミ{\k18}と{\k32}い{\k49}う{\k28}{\k37}フ{\k32}ラ{\k46}グ{\k32}解{\k35}除{\k50}が +Dialogue: 0,0:00:29.22,0:00:34.50,Kanji,,0,0,0,,{\k33}笑{\k66}顔{\k30}{\k73}救{\k59}え{\k64}る{\k67}の{\k29}{\k19}な{\k88}ら +Dialogue: 0,0:00:35.06,0:00:40.09,Kanji,,0,0,0,,{\k34}夏{\k15}の{\k65}風{\k12}い{\k40}ざ{\k14}な{\k50}う{\k32}{\k29}白{\k50}昼{\k25}の{\k23}ス{\k33}コ{\k15}ー{\k66}ル +Dialogue: 0,0:00:40.84,0:00:44.80,Kanji,,0,0,0,,{\k34}波{\k76}長{\k16}{\k76}シ{\k96}ン{\k12}ク{\k86}ロ +Dialogue: 0,0:00:45.41,0:00:50.30,Kanji,,0,0,0,,{\k21}ロ{\k27}マン{\k36}チ{\k33}ック{\k16}み{\k36}た{\k19}い{\k40}に{\k35}{\k17}気{\k16}の{\k16}利{\k30}い{\k33}た{\k49}言{\k19}葉{\k46}も +Dialogue: 0,0:00:50.58,0:00:59.34,Kanji,,0,0,0,,{\k37}見{\k17}つ{\k47}か{\k19}ら{\k46}ない{\k19}け{\k36}ど{\k45}{\k60}青{\k69}空{\k29}を{\k31}{\k32}目{\k31}指{\k178}す{\k56}{\k14}か{\k110}ら +Dialogue: 0,0:00:59.70,0:01:04.24,Kanji,,0,0,0,,{\k52}僕{\k63}達{\k85}は{\k27}{\k31}一{\k36}つ{\k17}に{\k31}な{\k32}れ{\k80}る +Dialogue: 0,0:01:04.56,0:01:09.78,Kanji,,0,0,0,,{\k22}戸{\k46}惑{\k16}い{\k35}の{\k161}涙{\k43}も{\k34}{\k65}夢{\k100}も +Dialogue: 0,0:01:10.00,0:01:15.96,Kanji,,0,0,0,,{\k57}真{\k62}実{\k84}に{\k26}{\k24}引{\k10}き{\k29}寄{\k27}せ{\k31}ら{\k30}れ{\k115}る{\k17}も{\k84}の +Dialogue: 0,0:01:16.20,0:01:24.48,Kanji,,0,0,0,,{\k33}そ{\k55}れ{\k26}は{\k24}{\k22}愛{\k48}し{\k62}す{\k19}ぎ{\k85}る{\k108}{\k40}魔{\k99}法{\k32}の{\k18}キ{\k32}ー{\k101}ワー{\k24}ド +Comment: 0,0:00:13.82,0:00:14.22,Default,,0,0,0,, +Dialogue: 0,0:00:14.24,0:00:24.23,Translation,,0,0,0,,Guarda oltre le parole e cerca la chiave per il mio cuore, +Dialogue: 0,0:00:24.68,0:00:34.50,Translation,,0,0,0,,se credi che il mio affetto possa renderti felice. +Dialogue: 0,0:00:35.06,0:00:40.09,Translation,,0,0,0,,La brezza di tarda estate si trasforma in una tempesta tumultuosa, +Dialogue: 0,0:00:40.84,0:00:44.80,Translation,,0,0,0,,mentre entriamo in sintonia. +Dialogue: 0,0:00:45.41,0:00:50.30,Translation,,0,0,0,,Sono un pessimo romantico, mi mancano le parole giuste, +Dialogue: 0,0:00:50.58,0:00:59.34,Translation,,0,0,0,,eppure sto ancora puntando al cielo, così sconfinato... +Dialogue: 0,0:00:59.70,0:01:04.24,Translation,,0,0,0,,Possiamo diventare una cosa sola +Dialogue: 0,0:01:04.56,0:01:09.78,Translation,,0,0,0,,mentre i nostri sogni e le nostre lacrime confuse +Dialogue: 0,0:01:10.00,0:01:15.96,Translation,,0,0,0,,ci portano sempre più vicini alla verità, +Dialogue: 0,0:01:16.20,0:01:24.48,Translation,,0,0,0,,che ormai è divenuta la nostra amata parola chiave magica. diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_ass.py b/tests/test_ass.py new file mode 100644 index 00000000..dc9e3572 --- /dev/null +++ b/tests/test_ass.py @@ -0,0 +1,54 @@ +import os +import pytest +from pyonfx import * + +# Get ass path +dir_path = os.path.dirname(os.path.realpath(__file__)) +path_ass = os.path.join(dir_path, "Ass", "in.ass") + +# Extract infos from ass file +io = Ass(path_ass) +meta, styles, lines = io.get_data() + +def test_meta_values(): + # Tests if all the meta values are taken correctly + assert meta.wrap_style == 0 + assert meta.scaled_border_and_shadow == True + assert meta.play_res_x == 1280 + assert meta.play_res_y == 720 + assert meta.audio == "" + assert meta.video == "?dummy:23.976000:2250:1920:1080:11:135:226:c" + +def test_line_values(): + # Tests if all the line values are taken correctly + line = lines[1] + assert line.comment == False + assert line.layer == 0 + assert line.start_time == Convert.time("0:00:14.24") + assert line.end_time == Convert.time("0:00:24.23") + assert line.duration == Convert.time("0:00:24.23") - Convert.time("0:00:14.24") + assert line.leadin == 1000.1 + assert line.leadout == lines[2].start_time - lines[1].end_time + assert line.style == "Romaji" + assert line.actor == "" + assert line.margin_l == 0 + assert line.margin_r == 0 + assert line.margin_v == 0 + assert line.effect == "" + assert line.text == "{\\k56}su{\\k13}re{\\k22}chi{\\k36}ga{\\k48}u{\\k25} {\\k34}ko{\\k33}to{\\k50}ba {\\k15}no {\\k17}u{\\k34}ra {\\k46}ni{\\k33} {\\k28}to{\\k36}za{\\k65}sa{\\k33}{\\k30}re{\\k51}ta{\\k16} {\\k33}ko{\\k33}ko{\\k78}ro {\\k15}no {\\k24}ka{\\k95}gi" + assert line.text_stripped == "surechigau kotoba no ura ni tozasareta kokoro no kagi" + # Values taken from YutilsCore to test + assert line.width == 941.703125 + assert line.height == 48.0 + assert line.ascent == 36.984375 + assert line.descent == 11.015625 + assert line.internal_leading == 13.59375 + assert line.external_leading == 3.09375 + assert line.x == line.center + assert line.y == line.top + assert line.left == 169.1484375 + assert line.center == 640.0 + assert line.right == 1110.8515625 + assert line.top == 25.0 + assert line.middle == 49.0 + assert line.bottom == 73.0