Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
naghim committed Mar 7, 2024
0 parents commit 2a66720
Show file tree
Hide file tree
Showing 16 changed files with 373 additions and 0 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Build

on: [push, repository_dispatch, workflow_dispatch]

jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Visual Studio
uses: ilammy/msvc-dev-cmd@v1
- name: Setup Python
uses: actions/setup-python@v3
with:
python-version: "3.11"
architecture: "x64"
- name: Install Python requirements
shell: bash
run: >
python -OO -m pip install --disable-pip-version-check --upgrade nuitka zstandard &&
python -OO -m pip install --disable-pip-version-check -r requirements.txt
- name: Build executable
shell: powershell
run: >
python -OO -m nuitka --standalone --onefile --python-flag=-OO --assume-yes-for-downloads --static-libpython=auto --windows-disable-console --windows-icon-from-ico=resources/subassistant.ico --windows-product-name=SubAssistant --windows-company-name=naghim --windows-file-version=1.0.0.0 --windows-file-description=SubAssistant --enable-plugin=pyside6 -o SubAssistant.exe subassistant/__main__.py
- uses: actions/upload-artifact@v3
with:
name: SubAssistant
path: SubAssistant.exe
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.py[c|o]
__pycache__
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pyside6
Binary file added resources/about.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/curly_braces.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/curly_braces_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/no_curly_braces.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions resources/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#FileChooserButton {
background-color: #555555;
border: none;
color: white;
padding: 10px 20px;
text-align: center;
text-decoration: none;
font-size: 16px;
margin: 4px 2px;
border-radius: 8px;
}

#Action_btn {
background-color: #d9073f;
border: none;
font-weight: bold;
color: #f5f5ff;
padding: 10px 20px;
text-align: center;
text-decoration: none;
font-size: 16px;
margin: 4px 2px;
border-radius: 8px;
}

#Label_txt {
color: #333;
font-size: 16px;
}

#Label_txt_bold {
color: #333;
font-size: 18px;
font-weight: bold;
}

#Label_txt_bold_mini {
color: #333;
font-size: 16px;
font-weight: bold;
}

#LineEdit {
border: 2px solid #e9e9e9;
border-radius: 8px;
padding: 5px;
font-size: 16px;
background-color: #ffffff;
}

#About_txt {
color: #333;
font-size: 16px;
text-align: justify;
}
Binary file added resources/subassistant.ico
Binary file not shown.
Empty file added subassistant/__init__.py
Empty file.
13 changes: 13 additions & 0 deletions subassistant/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import sys
from PySide6.QtWidgets import QApplication
from subassistant.gui.window import SubAssistantWindow

if __name__ == "__main__":
app = QApplication(sys.argv)

with open("./resources/styles.css", "r") as f:
app.setStyleSheet(f.read())

window = SubAssistantWindow()
window.show()
sys.exit(app.exec())
Empty file added subassistant/gui/__init__.py
Empty file.
157 changes: 157 additions & 0 deletions subassistant/gui/tab.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QFileDialog, QHBoxLayout, QLineEdit, QScrollArea, QMessageBox
from subassistant.logic.logic import RemoveComments, CommentDialogue
import os

class BaseSubtitleUI(QWidget):
def __init__(self):
super().__init__()

self.layout = QVBoxLayout()
self.window_title = QLabel(self.TAB_TITLE)
self.window_title.setObjectName("Label_txt_bold")
self.layout.addWidget(self.window_title)

self.input_file_label = QLabel("Select Input File:")
self.input_file_label.setObjectName("Label_txt")
self.layout.addWidget(self.input_file_label)

self.input_file_line_edit = QLineEdit()
self.input_file_line_edit.setReadOnly(True)
self.input_file_line_edit.setObjectName("LineEdit")

self.input_btn = QPushButton("Browse", clicked=self.get_file)
self.input_btn.setObjectName("FileChooserButton")

input_file_layout = QHBoxLayout()
input_file_layout.addWidget(self.input_file_line_edit)
input_file_layout.addWidget(self.input_btn)

self.layout.addLayout(input_file_layout)

self.output_label = QLabel("Output Folder:")
self.output_label.setObjectName("Label_txt")
self.layout.addWidget(self.output_label)

self.output_file_line_edit = QLineEdit()
self.output_file_line_edit.setObjectName("LineEdit")
output_file_layout = QHBoxLayout()
output_file_layout.addWidget(self.output_file_line_edit)

self.output_btn = QPushButton("Browse", clicked=self.get_output_folder)
self.output_btn.setObjectName("FileChooserButton")
output_file_layout.addWidget(self.output_btn)

self.layout.addLayout(output_file_layout)

self.action_button = QPushButton(self.ACTION)
self.action_button.setObjectName("Action_btn")
self.action_button.clicked.connect(lambda: self.process_file(self.ACTION_CLASS))

self.layout.addWidget(self.action_button)
self.setLayout(self.layout)

def get_file(self):
filename, _ = QFileDialog.getOpenFileName(self, "Select File", filter="Subtitles (*.ass)")
if filename:
self.input_file_line_edit.setText(filename)
self.output_file_line_edit.setText(self.get_output_file(self.output_file_line_edit.text()))

def get_output_folder(self):
output_folder = QFileDialog.getExistingDirectory(self, "Select Folder")
if output_folder:
self.output_file_line_edit.setText(self.get_output_file(output_folder))

def get_output_file(self, folder):
input_file = self.input_file_line_edit.text()

if input_file:
input_filename, input_ext = os.path.splitext(os.path.basename(input_file))

if not folder:
folder = os.path.dirname(input_file)
if folder.endswith(".ass"):
folder = os.path.dirname(folder)
output_filename = f'{input_filename}_modified{input_ext}'
output_file = os.path.join(folder, output_filename)
else:
output_file = folder

output_file = output_file.replace(os.sep, '/')
return output_file

def process_file(self, action_class):
input_file = self.input_file_line_edit.text()
output_folder = self.output_file_line_edit.text()

if not input_file:
QMessageBox.warning(self, "Warning", "Please select a file to process.")
return

if not output_folder:
QMessageBox.warning(self, "Warning", "Please select an output folder.")
return

output_file = self.get_output_file(output_folder)

try:
action_class(input_file, output_file).process_file()
QMessageBox.information(self, "Success", f'File processed successfully.\nOutput file: {output_file}')
except Exception as e:
QMessageBox.critical(self, "Error", f'An error occurred while processing the file.\nError: {e}')

class CommentTab(BaseSubtitleUI):
ACTION = "Comment Dialogue"
TAB_TITLE = "Comment Subtitle Text"
ACTION_CLASS = CommentDialogue


class RemoveCommentTab(BaseSubtitleUI):
ACTION = "Delete Comments"
TAB_TITLE = "Delete Commented Dialogue"
ACTION_CLASS = RemoveComments


class AboutTab(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.layout = QVBoxLayout()

self.content_widget = QWidget()
self.content_layout = QVBoxLayout()

scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setWidget(self.content_widget)

self.layout.addWidget(scroll_area)


self.text_logo = QLabel("<html><head/><body><p align='center'>Sub<span style=\"color: #d9073f\">Assistant</span></p></body></html>"
)

self.text = QLabel("<html><head/><body><p align='justify'><b> 👀 What is this?</b> This is a specialized desktop application designed to simplify the translation process for subtitle files, specifically .ass (Advanced SubStation Alpha) format. Tailored for translators, CommentOut facilitates seamless collaboration by allowing users to comment out the original English dialogue text, write their translations alongside it, and enable proofreaders or quality checkers to review both versions within the same file. By providing a unified platform for bilingual text management, this application enhances the efficiency and accuracy of subtitle translation workflows.</p></body></html>"
)

self.text_how_to = QLabel("<html><head/><body><p align='justify'><b> 👀 How to use?</b> Choose the action you wish to take from the side menu—whether to comment out text or delete comments. Use the \"Select File\" button to open the .ass file. The program will automatically propose an output filename within the same folder. If you prefer a different folder or wish to rename the output, utilize the \"Browse\" button or directly edit the output path. Upon clicking the button, the selected operation will be executed.</p></body></html>"
)

self.text_slogan = QLabel("<html><head/><body><p align='center'>Made with 50% 💕 and 50% ☕ <br/> by <a style=\"color: #d9073f\" href=\"https://github.com/naghim\">naghim @ GitHub</a></p></body></html>"
)

self.text_slogan.setOpenExternalLinks(True)
self.text.setWordWrap(True)
self.text_how_to.setWordWrap(True)
self.text.setObjectName("About_txt")
self.text_how_to.setObjectName("About_txt")
self.text_slogan.setObjectName("Label_txt_bold_mini")
self.text_logo.setObjectName("Label_txt_bold")
self.content_layout.addWidget(self.text_logo)
self.content_layout.addWidget(self.text_how_to)
self.content_layout.addWidget(self.text)
self.content_layout.addWidget(self.text_slogan)
self.content_widget.setLayout(self.content_layout)

self.setLayout(self.layout)
28 changes: 28 additions & 0 deletions subassistant/gui/window.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import sys
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QTabWidget, QWidget, QVBoxLayout
from subassistant.gui.tab import CommentTab, RemoveCommentTab, AboutTab

class SubAssistantWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("SubAssistant")
self.setWindowIcon(QIcon("./resources/curly_braces_icon.png"))
self.setFixedSize(550, 350)
self.initUI()

def initUI(self):
layout = QVBoxLayout()

self.tab_widget = QTabWidget()
self.tab_widget.setTabPosition(QTabWidget.West)

self.tab_widget.addTab(CommentTab(), QIcon("./resources/curly_braces.png"), "")
self.tab_widget.addTab(RemoveCommentTab(), QIcon("./resources/no_curly_braces.png"), "")
self.tab_widget.addTab(AboutTab(), QIcon("./resources/about.png"), "")


self.tab_widget.setStyleSheet("QTabBar::tab { height: 60px; width: 100px;}")
layout.addWidget(self.tab_widget)

self.setLayout(layout)
Empty file added subassistant/logic/__init__.py
Empty file.
87 changes: 87 additions & 0 deletions subassistant/logic/logic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import sys, os

class ProcessSubtitles(object):
def __init__(self, input_file: str, output_file: str) -> None:
self.input_file = input_file
self.output_file = output_file

def process_file(self) -> None:
with open(self.input_file, 'r', encoding='utf-8') as infile, open(self.output_file, 'w', encoding='utf-8') as outfile:
for line in infile:
modified_line = self.process_line(line)
outfile.write(modified_line)

def process_line(self, line: str) -> str:
raise NotImplementedError



class RemoveComments(ProcessSubtitles):
def process_line(self, line: str) -> str:
modified_line = ''
inside_braces = False

for i, char in enumerate(line):
if char == '{' and line[i + 1] != '\\':
inside_braces = True
elif char == '}' and inside_braces:
inside_braces = False
elif not inside_braces:
modified_line += char

return modified_line

def process_file(self) -> None:
super().process_file()
print("Text within {} removed and written to", self.output_file)

class CommentDialogue(ProcessSubtitles):
def process_line(self, line: str) -> str:
if not line.startswith("Dialogue"):
return line

modified_line = ''
inside_braces = False
comma_count = 0
str_buffer = ''

for i, char in enumerate(line.strip()):
if char == ',':
comma_count += 1
if comma_count == 9:
modified_line += char
continue

# Check if the text part has started
if comma_count >= 9:
# Don't comment out the text if it's a formatting tag
if char == '{':
# But if the opening brace was preceeded by dialog, we want to comment that part out
inside_braces = True
if str_buffer != '':
modified_line += f"{{{str_buffer}}}"
modified_line += char
elif char == '}':
inside_braces = False
modified_line += char

# Buffer the text outside of the braces to comment out later
elif not inside_braces:
str_buffer += char

# We don't want to modify the line until the actual text part
else:
modified_line += char

else:
modified_line += char

if str_buffer != '':
modified_line += f"{{{str_buffer}}}"

return f"{modified_line}\n"

def process_file(self) -> None:
super().process_file()
print("Subtitle successfully commented and saved to ", self.output_file)

0 comments on commit 2a66720

Please sign in to comment.