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

Fix #93 ffmpeg parser #394

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
11 changes: 10 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,14 @@
"name": "Python Development",
"postCreateCommand": "bash .devcontainer/setup.sh",
"waitFor": "postCreateCommand",
"workspaceFolder": "/workspaces/typed-ffmpeg"
"workspaceFolder": "/workspaces/typed-ffmpeg",// Build the volume mount for gcloud config.
"mounts": [
// 把本機的 gcloud config 綁到 container 內使用
"source=${localEnv:HOME}/.config/gcloud,target=/root/.config/gcloud,type=bind,consistency=cached",
// for ssh login to github
"source=${localEnv:HOME}/.ssh,target=/root/.ssh,type=bind,consistency=cached",
// 為了在不同 devcontainer 切換時,保留相同的 bash history
"source=gstudio-zshhistory,target=/root/commandhistory,type=volume",
"source=poetry-virtualenvs,target=/root/.cache/pypoetry/virtualenvs,type=volume"
],
}
Empty file added src/app/__init__.py
Empty file.
67 changes: 67 additions & 0 deletions src/app/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import shlex
from scripts.cache import load
from ffmpeg.common.schema import FFMpegOptionList, FFMpegOptionType, FFMpegOption
from ffmpeg.base import input, output
from typing import Any

option_list = load(FFMpegOptionList, "list")



def parse(cmd: str) -> Any:
"""
Parse the command line arguments and return the options.
Options has Input Option, Output Option, Global Option, and Filter Option.
"""

option_dict = {
k.name: k for k in option_list.options
}

arguments = shlex.split(cmd)

assert arguments.pop(0) == "ffmpeg"

parsed_options: dict[str, tuple[FFMpegOption, str | bool]] = {}

nodes = []
while arguments:
arg = arguments.pop(0)
if arg == "-i":
filename = arguments.pop(0)

nodes.append(input(filename=filename, **{k: parsed_options[k] for k in parsed_options if parsed_options[k][0].is_input_option}))
parsed_options = {k: parsed_options[k] for k in parsed_options if not parsed_options[k][0].is_input_option}

assert not {k: parsed_options[k] for k in parsed_options if parsed_options[k][0].is_output_option}, f"Output options must not be specified before input files, {parsed_options}"

elif arg.startswith("-"):
arg = arg.lstrip("-")
arg_name = arg.split(":")[0]
assert arg_name in option_dict, f"Unknown option {arg_name}"

option = option_dict[arg_name]

arg_value: str | bool
if option.type == FFMpegOptionType.OPT_TYPE_BOOL:
arg_value = True
else:
arg_value = arguments.pop(0)

# option is not exclusive, for example -c are both input and output options
parsed_options[arg] = (option, arg_value)
else:
# should be output file
nodes.append(output(filename=arg, **{k: parsed_options[k][1] for k in parsed_options if parsed_options[k][0].is_output_option}))
parsed_options = {k: parsed_options[k] for k in parsed_options if not parsed_options[k][0].is_output_option}

assert not {k: parsed_options[k] for k in parsed_options if parsed_options[k][0].is_input_option}, f"Input options must not be specified after output files, {parsed_options}"

if parsed_options:
# handle globla nodes
...

print(nodes)

parse("ffmpeg -i input.mp4 -c:v libx264 -c:a aac output.mp4")
parse("ffmpeg -i input.mp4 -c:a aac output.mp4 -c:v libx264 -c:a aac output.mp4")
5 changes: 5 additions & 0 deletions src/ffmpeg/common/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,3 +306,8 @@ def is_global_option(self) -> bool:
@property
def is_support_stream_specifier(self) -> bool:
return bool(self.flags & FFMpegOptionFlag.OPT_SPEC)


@dataclass(frozen=True, kw_only=True)
class FFMpegOptionList:
options: tuple[FFMpegOption, ...]
Loading
Loading