Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci: add lint with black and black all the code #43

Merged
merged 1 commit into from
Nov 18, 2022
Merged
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
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -36,6 +36,9 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install .
pip install pytest
pip install pytest black
- name: Run Test
run: pytest test/
- name: Run Lint
run: black . --check

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -216,12 +216,12 @@ autocut
代码格式目前是遵循 PEP-8,变量命名尽量语义化即可。

在开发完成之后,最重要的一点是需要进行**测试**,请保证提交之前对所有**与你修改直接相关的部分**以及**你修改会影响到的部分**都进行了测试,并保证功能的正常。
目前没有测试用例的CI,会在之后进行完善。
目前使用 `GitHub Actions` CI, Lint 使用 black 提交前请运行 `black`

### 提交

1. commit 信息用英文描述清楚你做了哪些修改即可,小写字母开头。
2. 最好可以保证一次的 commit 涉及的修改比较小,可以简短地描述清楚,这样也方便之后有修改时的查找。
3. PR 的时候 title 简述有哪些修改, contents 可以具体写下修改内容。
4. run test `pip install pytest` then `pytest test`

4. run lint `pip install black` then `black .`
2 changes: 1 addition & 1 deletion autocut/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.0.3'
__version__ = "0.0.3"
67 changes: 39 additions & 28 deletions autocut/cut.py
Original file line number Diff line number Diff line change
@@ -22,21 +22,24 @@ def write_md(self, videos):

md.clear()
md.add_done_editing(False)
md.add('\nSelect the files that will be used to generate `autocut_final.mp4`\n')
md.add("\nSelect the files that will be used to generate `autocut_final.mp4`\n")
base = lambda fn: os.path.basename(fn)
for f in videos:
md_fn = utils.change_ext(f, 'md')
md_fn = utils.change_ext(f, "md")
video_md = utils.MD(md_fn, self.args.encoding)
# select a few words to scribe the video
desc = ''
desc = ""
if len(video_md.tasks()) > 1:
for _, t in video_md.tasks()[1:]:
m = re.findall(r'\] (.*)', t)
if m and 'no speech' not in m[0].lower():
desc += m[0] + ' '
m = re.findall(r"\] (.*)", t)
if m and "no speech" not in m[0].lower():
desc += m[0] + " "
if len(desc) > 50:
break
md.add_task(False, f'[{base(f)}]({base(md_fn)}) {"[Edited]" if video_md.done_editing() else ""} {desc}')
md.add_task(
False,
f'[{base(f)}]({base(md_fn)}) {"[Edited]" if video_md.done_editing() else ""} {desc}',
)
md.write()

def run(self):
@@ -49,20 +52,22 @@ def run(self):
for m, t in md.tasks():
if not m:
continue
m = re.findall(r'\[(.*)\]', t)
m = re.findall(r"\[(.*)\]", t)
if not m:
continue
fn = os.path.join(os.path.dirname(md_fn), m[0])
logging.info(f'Loading {fn}')
logging.info(f"Loading {fn}")
videos.append(editor.VideoFileClip(fn))

dur = sum([v.duration for v in videos])
logging.info(f'Merging into a video with {dur / 60:.1f} min length')
logging.info(f"Merging into a video with {dur / 60:.1f} min length")

merged = editor.concatenate_videoclips(videos)
fn = os.path.splitext(md_fn)[0] + '_merged.mp4'
merged.write_videofile(fn, audio_codec='aac', bitrate=self.args.bitrate) # logger=None,
logging.info(f'Saved merged video to {fn}')
fn = os.path.splitext(md_fn)[0] + "_merged.mp4"
merged.write_videofile(
fn, audio_codec="aac", bitrate=self.args.bitrate
) # logger=None,
logging.info(f"Saved merged video to {fn}")


# Cut videos
@@ -71,30 +76,30 @@ def __init__(self, args):
self.args = args

def run(self):
fns = {'srt': None, 'video': None, 'md': None}
fns = {"srt": None, "video": None, "md": None}
for fn in self.args.inputs:
ext = os.path.splitext(fn)[1][1:]
fns[ext if ext in fns else 'video'] = fn
fns[ext if ext in fns else "video"] = fn

assert fns['video'], 'must provide a video filename'
assert fns['srt'], 'must provide a srt filename'
assert fns["video"], "must provide a video filename"
assert fns["srt"], "must provide a srt filename"

output_fn = utils.change_ext(utils.add_cut(fns['video']), 'mp4')
output_fn = utils.change_ext(utils.add_cut(fns["video"]), "mp4")
if utils.check_exists(output_fn, self.args.force):
return

with open(fns['srt'], encoding=self.args.encoding) as f:
with open(fns["srt"], encoding=self.args.encoding) as f:
subs = list(srt.parse(f.read()))

if fns['md']:
md = utils.MD(fns['md'], self.args.encoding)
if fns["md"]:
md = utils.MD(fns["md"], self.args.encoding)
if not md.done_editing():
return
index = []
for mark, sent in md.tasks():
if not mark:
continue
m = re.match(r'\[(\d+)', sent.strip())
m = re.match(r"\[(\d+)", sent.strip())
if m:
index.append(int(m.groups()[0]))
subs = [s for s in subs if s.index in index]
@@ -106,9 +111,11 @@ def run(self):
# Avoid disordered subtitles
subs.sort(key=lambda x: x.start)
for x in subs:
segments.append({'start': x.start.total_seconds(), 'end': x.end.total_seconds()})
segments.append(
{"start": x.start.total_seconds(), "end": x.end.total_seconds()}
)

video = editor.VideoFileClip(fns['video'])
video = editor.VideoFileClip(fns["video"])

# Add a fade between two clips. Not quite necessary. keep code here for reference
# fade = 0
@@ -117,14 +124,18 @@ def run(self):
# s['start'], s['end']).crossfadein(fade) for s in segments]
# final_clip = editor.concatenate_videoclips(clips, padding = -fade)

clips = [video.subclip(s['start'], s['end']) for s in segments]
clips = [video.subclip(s["start"], s["end"]) for s in segments]
final_clip = editor.concatenate_videoclips(clips)
logging.info(f'Reduced duration from {video.duration:.1f} to {final_clip.duration:.1f}')
logging.info(
f"Reduced duration from {video.duration:.1f} to {final_clip.duration:.1f}"
)

aud = final_clip.audio.set_fps(44100)
final_clip = final_clip.without_audio().set_audio(aud)
final_clip = final_clip.fx(editor.afx.audio_normalize)

# an alternative to birate is use crf, e.g. ffmpeg_params=['-crf', '18']
final_clip.write_videofile(output_fn, audio_codec='aac', bitrate=self.args.bitrate)
logging.info(f'Saved video to {output_fn}')
final_clip.write_videofile(
output_fn, audio_codec="aac", bitrate=self.args.bitrate
)
logging.info(f"Saved video to {output_fn}")
14 changes: 8 additions & 6 deletions autocut/daemon.py
Original file line number Diff line number Diff line change
@@ -13,28 +13,30 @@ def __init__(self, args):
self.sleep = 1

def run(self):
assert len(self.args.inputs) == 1, 'Must provide a single folder'
assert len(self.args.inputs) == 1, "Must provide a single folder"
while True:
self._iter()
time.sleep(self.sleep)
self.sleep = min(60, self.sleep + 1)

def _iter(self):
folder = self.args.inputs[0]
files = sorted(list(glob.glob(os.path.join(folder, '*'))))
files = sorted(list(glob.glob(os.path.join(folder, "*"))))
videos = [f for f in files if utils.is_video(f)]
args = copy.deepcopy(self.args)
for f in videos:
srt_fn = utils.change_ext(f, 'srt')
md_fn = utils.change_ext(f, 'md')
srt_fn = utils.change_ext(f, "srt")
md_fn = utils.change_ext(f, "md")
if srt_fn not in files or md_fn not in files:
args.inputs = [f]
try:
transcribe.Transcribe(args).run()
self.sleep = 1
break
except RuntimeError as e:
logging.warn('Failed, may be due to the video is still on recording')
logging.warn(
"Failed, may be due to the video is still on recording"
)
pass
if md_fn in files:
if utils.add_cut(md_fn) in files:
@@ -47,7 +49,7 @@ def _iter(self):
self.sleep = 1
break

args.inputs = [os.path.join(folder, 'autocut.md')]
args.inputs = [os.path.join(folder, "autocut.md")]
merger = cut.Merger(args)
merger.write_md(videos)
merger.run()
124 changes: 87 additions & 37 deletions autocut/main.py
Original file line number Diff line number Diff line change
@@ -6,71 +6,121 @@


def main():
parser = argparse.ArgumentParser(description='Edit videos based on transcribed subtitles',
formatter_class=argparse.RawDescriptionHelpFormatter)
parser = argparse.ArgumentParser(
description="Edit videos based on transcribed subtitles",
formatter_class=argparse.RawDescriptionHelpFormatter,
)

logging.basicConfig(format='[autocut:%(filename)s:L%(lineno)d] %(levelname)-6s %(message)s')
logging.basicConfig(
format="[autocut:%(filename)s:L%(lineno)d] %(levelname)-6s %(message)s"
)
logging.getLogger().setLevel(logging.INFO)

parser.add_argument('inputs', type=str, nargs='+',
help='Inputs filenames/folders')
parser.add_argument('-t', '--transcribe', help='Transcribe videos/audio into subtitles',
action=argparse.BooleanOptionalAction)
parser.add_argument('-c', '--cut', help='Cut a video based on subtitles',
action=argparse.BooleanOptionalAction)
parser.add_argument('-d', '--daemon', help='Monitor a folder to transcribe and cut',
action=argparse.BooleanOptionalAction)
parser.add_argument('-s', help='Convert .srt to a compact format for easier editing',
action=argparse.BooleanOptionalAction)
parser.add_argument('-m', '--to-md', help='Convert .srt to .md for easier editing',
action=argparse.BooleanOptionalAction)
parser.add_argument('--lang', type=str, default='zh',
choices=['zh', 'en'],
help='The output language of transcription')
parser.add_argument('--prompt', type=str, default='',
help='initial prompt feed into whisper')
parser.add_argument('--whisper-model', type=str, default='small',
choices=['tiny', 'base', 'small', 'medium', 'large'],
help='The whisper model used to transcribe.')
parser.add_argument('--bitrate', type=str, default='10m',
help='The bitrate to export the cutted video, such as 10m, 1m, or 500k')
parser.add_argument('--vad', help='If or not use VAD',
action=argparse.BooleanOptionalAction)
parser.add_argument('--force', help='Force write even if files exist',
action=argparse.BooleanOptionalAction)
parser.add_argument('--encoding', type=str, default='utf-8',
help='Document encoding format')
parser.add_argument('--device', type=str, default=None,
choices=['cpu', 'cuda'],
help='Force to CPU or GPU for transcribing. In default automatically use GPU if available.')
parser.add_argument("inputs", type=str, nargs="+", help="Inputs filenames/folders")
parser.add_argument(
"-t",
"--transcribe",
help="Transcribe videos/audio into subtitles",
action=argparse.BooleanOptionalAction,
)
parser.add_argument(
"-c",
"--cut",
help="Cut a video based on subtitles",
action=argparse.BooleanOptionalAction,
)
parser.add_argument(
"-d",
"--daemon",
help="Monitor a folder to transcribe and cut",
action=argparse.BooleanOptionalAction,
)
parser.add_argument(
"-s",
help="Convert .srt to a compact format for easier editing",
action=argparse.BooleanOptionalAction,
)
parser.add_argument(
"-m",
"--to-md",
help="Convert .srt to .md for easier editing",
action=argparse.BooleanOptionalAction,
)
parser.add_argument(
"--lang",
type=str,
default="zh",
choices=["zh", "en"],
help="The output language of transcription",
)
parser.add_argument(
"--prompt", type=str, default="", help="initial prompt feed into whisper"
)
parser.add_argument(
"--whisper-model",
type=str,
default="small",
choices=["tiny", "base", "small", "medium", "large"],
help="The whisper model used to transcribe.",
)
parser.add_argument(
"--bitrate",
type=str,
default="10m",
help="The bitrate to export the cutted video, such as 10m, 1m, or 500k",
)
parser.add_argument(
"--vad", help="If or not use VAD", action=argparse.BooleanOptionalAction
)
parser.add_argument(
"--force",
help="Force write even if files exist",
action=argparse.BooleanOptionalAction,
)
parser.add_argument(
"--encoding", type=str, default="utf-8", help="Document encoding format"
)
parser.add_argument(
"--device",
type=str,
default=None,
choices=["cpu", "cuda"],
help="Force to CPU or GPU for transcribing. In default automatically use GPU if available.",
)

args = parser.parse_args()

if args.transcribe:
from .transcribe import Transcribe

Transcribe(args).run()
elif args.to_md:
from .utils import trans_srt_to_md

if len(args.inputs) == 2:
[input_1, input_2] = args.inputs
base, ext = os.path.splitext(input_1)
if ext != '.srt':
if ext != ".srt":
input_1, input_2 = input_2, input_1
trans_srt_to_md(args.encoding, args.force, input_1, input_2)
elif len(args.inputs) == 1:
trans_srt_to_md(args.encoding, args.force, args.inputs[0])
else:
logging.warn('Wrong number of files, please pass in a .srt file or an additional video file')
logging.warn(
"Wrong number of files, please pass in a .srt file or an additional video file"
)
elif args.cut:
from .cut import Cutter

Cutter(args).run()
elif args.daemon:
from .daemon import Daemon

Daemon(args).run()
elif args.s:
utils.compact_rst(args.inputs[0], args.encoding)
else:
logging.warn('No action, use -c, -t or -d')
logging.warn("No action, use -c, -t or -d")


if __name__ == "__main__":
Loading