Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/lint-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v6
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v6
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v6
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v6
Expand Down
20 changes: 16 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,25 +1,37 @@
# Python 和工具缓存
__pycache__
.pytest_cache
.mypy_cache
.coverage

# 虚拟环境
.venv
htmlcov

# 构建输出
dist
Annotations2Sub.egg-info

# 覆盖率报告
htmlcov

# 杂项
测试用例/
src/tests/garbage/*
!src/tests/garbage/stub
report
*.mp4
*.mkv
*.webm
*.xml
*.ass

src/tests/garbage/*
!src/tests/garbage/stub

report
*.lnk
*.ps1
*.pyz
*.dot
*.prof

mypy.ini
uv.lock
.python-version
5 changes: 4 additions & 1 deletion DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,6 @@ subtitles_string = AnnotationsXmlStringToSubtitlesString(xml_string)
with open('output.ass', 'w', encoding='utf-8') as f:
f.write(subtitles_string)

# If you're concerned about licensing issues, you can contact me for an alternative license.
```

### Comparison with Similar Software
Expand Down Expand Up @@ -502,3 +501,7 @@ Metadata database of videos with annotations.
[Annotations2Sub-Lite](https://a2s.liutao.page/)

A web version made by AI.

[Ar tonelico 系列 中文字幕 (Rain Shimotsuki 注释字幕还原) 合集](https://www.bilibili.com/video/BV1Ff4y1t7Dj)

This project is a derivative of this project.
64 changes: 22 additions & 42 deletions src/Annotations2Sub/Annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import datetime as dt
import math
import re
from datetime import datetime
from typing import List, Optional, Union
from xml.etree.ElementTree import Element
Expand Down Expand Up @@ -192,55 +193,36 @@ def ParseAnnotationColor(colorString: str) -> Color:
return Color(red=r, green=g, blue=b)

def ParseTime(timeString: str) -> datetime:
def parseFloat(string: str) -> float:
def cleanInt(string: str) -> str:
string = string.replace("s", "")
string = string.replace("-", "")
string = string.replace("%", "")

if string == "NaN":
return "0"
if string == "aN":
return "0"
if "#" in string:
return "0"
return string

if string == "":
return 0
if string == "4294967294":
return 0
if string == "&":
return 0
if string == "NaN":
return 0

part = string.split(".")
part = list(map(cleanInt, part))
string = part[0]
if len(part) > 1:
string = string + "." + part[1]
return float(string)

if timeString == "":
return datetime.strptime("0", "%S")
if timeString == "never":
return datetime.strptime("0", "%S")
if timeString == "undefined":
return datetime.strptime("0", "%S")

parts = timeString.split(":")
seconds = 0.0

for part in parts:
time = parseFloat(part)
time = ParseFloat(part)
seconds = 60 * seconds + abs(time)

return datetime.fromtimestamp(seconds, dt.timezone.utc).replace(tzinfo=None)

def ParseFloat(string: str) -> float:
string = string.replace(",", ".")
return float(string)
def parseFloat(string: str) -> float:
match = re.match(
r"[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?", string.lstrip()
)
if match != None:
number = float(match.group(0))
return number
return 0.0

try:
number = float(string)
except ValueError:
number = parseFloat(string)

if math.isnan(number):
return 0.0
if number > 2147483647:
return 0.0

return number

_id = each.get("id", "")
if _id == "":
Expand Down Expand Up @@ -304,8 +286,6 @@ def ParseFloat(string: str) -> float:

if w < 0:
w = 0
if math.isnan(w):
w = 0

author = each.get("author", "")

Expand Down
5 changes: 5 additions & 0 deletions src/Annotations2Sub/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

此工具可以帮助您将 YouTube 注释转换为 ASS 字幕文件, 您可以播放或添加到视频中.
"""

"""
xml.
etree.
Expand Down Expand Up @@ -97,6 +98,10 @@

随着时间流逝, 本项目所依赖的外部服务已逐渐变得不可用, 现已移除相关功能. 感谢 Invidious 和 Internet Archive 所提供的帮助.

---

又是时光流逝 Vibe Code 已成现实, Agents 已成长的他妈都不认识.

---
- 注释(Annotations): YouTube 的功能
- SSA(Sub Station Alpha): 字幕格式
Expand Down
2 changes: 1 addition & 1 deletion src/Annotations2Sub/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@


def Run(args=None) -> int:
"""命令行应用的实现
"""命令行界面的实现

参数应当是 `list(str)`,
当参数为 `None` 时 `argparse` 会从 `sys.argv` 解析参数.
Expand Down
28 changes: 9 additions & 19 deletions src/Annotations2Sub/color.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,23 @@
# -*- coding: utf-8 -*-

from dataclasses import dataclass

from Annotations2Sub.i18n import _


@dataclass
class Color:
def __init__(
self,
red: int = 0,
green: int = 0,
blue: int = 0,
):
if red > 255:
raise ValueError(_('"red" 必须在 0-255 之间'))
if green > 255:
raise ValueError(_('"green" 必须在 0-255 之间'))
if blue > 255:
raise ValueError(_('"blue" 必须在 0-255 之间'))
self.red = red
self.green = green
self.blue = blue
red: int = 0
green: int = 0
blue: int = 0


@dataclass
class Alpha:
def __init__(self, alpha: int = 0):
if alpha > 255:
raise ValueError(_('"alpha" 必须在 0-255 之间'))
self.alpha = alpha
alpha: int = 0


@dataclass
class Rgba:
def __init__(self, color: Color = Color(), alpha: Alpha = Alpha()):
self.red = color.red
Expand Down
53 changes: 28 additions & 25 deletions src/README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
# Annotations2Sub Source Code

## 快速背景
## 概述

本仓库是一个用于将旧版 YouTube Annotations 的 XML 文件转换为 ASS 字幕文件的命令行工具. 包的入口点是控制台脚本 `Annotations2Sub`(在 `pyproject.toml` -> `project.scripts` 中定义). 主要代码位于 `src/Annotations2Sub/`, 测试代码位于 `src/tests/`.
Annotations2Sub 是一个将旧版 YouTube Annotations 的 XML 文件转换为 ASS 字幕文件的命令行工具. 入口点是控制台脚本 `Annotations2Sub`. 主要代码位于 `src/Annotations2Sub/`, 测试代码位于 `src/tests/`.

## 技术栈

- Python 3.7+
- 无外部依赖
- 使用 [uv](https://github.com/astral-sh/uv) 管理工具链, 使用 setuptools 打包, pytest 测试, mypy 类型检查, isort 和 black 进行代码格式化.

## 组织结构

- 目的: 读取 YouTube Annotations XML 文件并生成 ASS 字幕文件. 用户用法见 `README.md`: `Annotations2Sub <file.xml>`.
- 流程: 解析(`Annotations.py`)、转换(`convert.py`)、输出(`subtitles/*`).
- 流程: 解析(`Annotations.py`)、转换(`convert.py`)和输出(`subtitles/*`).
- 入口: `src/Annotations2Sub/_main.py` 或 `src/Annotations2Sub/__main__.py`.
- 核心模块:
- `src/Annotations2Sub/_main.py` 或 `src/Annotations2Sub/__main__.py` —— 程序入口.
- `src/Annotations2Sub/Annotations.py` —— XML 解析和Annotations数据结构.
- `src/Annotations2Sub/convert.py` —— 主要的转换逻辑和数据变换.
- `src/Annotations2Sub/subtitles/` —— 字幕格式、样式、事件和绘图辅助.
- `src/Annotations2Sub/Annotations.py` : XML 解析和Annotations数据结构.
- `src/Annotations2Sub/convert.py` : 主要转换逻辑.
- `src/Annotations2Sub/subtitles/` : 字幕格式、样式、事件和绘图辅助.
- `src/Annotations2Sub/cli.py` : 用户界面.
- 测试:
- `src/tests/test_Baseline.py` : 回归测试.
- `src/tests/test_cli.py` : 集成测试.
- `src/tests/test_addendum.py` : 以上两个测试未覆盖的测试.
- `src/tests/unittest/` : 其他测试.

## 项目特有的模式

- 测试用例是 Youtube Annotations, 使用 `src/tests/testCase/` 下的 `.test` 文件作为输入, 同时包含以 `.ass.test`、`.transform.ass.test` 等后缀的期望输出文件.
- gettext `.po`/`.mo` 文件在 `src/Annotations2Sub/locales/`. 如有用户可见字符串变更, 请更新 `.po` 文件并重新生成 `.mo`.

## 如何运行、测试和代码检查

- 推荐使用 [uv](https://github.com/astral-sh/uv) 进行依赖管理和运行.
- 使用 [uv](https://github.com/astral-sh/uv) 进行依赖管理.

`uv sync`

Expand All @@ -38,20 +54,7 @@

`black .`

## 项目特有的模式和约定

- 测试用例是 Youtube Annotations, 使用 `src/tests/testCase/` 下的 `.test` 文件作为输入, 同时包含以 `.ass.test`、`.transform.ass.test` 等后缀的期望输出文件.
- 添加类型注解并保持 mypy 检查通过.
- 使用 isort 和 black 进行代码格式化.
- 本地化: gettext `.po`/`.mo` 文件在 `src/Annotations2Sub/locales/`. 如有用户可见字符串变更, 请更新 `.po` 文件并重新生成 `.mo`.

## 集成点与外部依赖

- 无外部依赖.
- 构建/打包使用 setuptools.
- CI 会上传覆盖率到 Codecov.

## 调试注释行为
## 调试 Annotations 行为

使用[youtube_annotations_hack](https://github.com/USED255/youtube_annotations_hack)来预览正确的注释行为.

Expand All @@ -61,7 +64,7 @@

## 问题咨询

- 在 GitHub 提 [issue](https://github.com/USED255/Annotations2Sub/issues).
请在 GitHub 提 [issue](https://github.com/USED255/Annotations2Sub/issues).

## 您也可以看看

Expand All @@ -71,7 +74,7 @@

- https://github.com/weizhenye/ASS/wiki/ASS-字幕格式规范

整理过的关于 ASS 字幕文件的格式以及渲染行为的文档.
整理好的关于 ASS 字幕文件的格式以及渲染行为的文档.

- https://github.com/USED255/youtube_annotations_hack

Expand Down
Loading
Loading