1
1
import os
2
2
import sys
3
+ from dataclasses import dataclass
3
4
4
5
import av
5
6
import av ._core
7
+ from av .container import InputContainer , OutputContainer
6
8
7
- av .logging .set_level (av .logging .VERBOSE )
8
9
10
+ @dataclass (slots = True )
11
+ class AvLog :
12
+ level_set : int
9
13
10
- def remux (
11
- prompt : bool , vn : bool , an : bool , sn : bool , input_file : str , output_file : str
12
- ) -> None :
13
- try :
14
- input_container = av .open (input_file , "r" )
15
- except av .error .FileNotFoundError :
16
- sys .stderr .write (f"Error opening input file { input_file } .\n " )
17
- sys .exit (1 )
14
+ def level (self , val : int , msg : str ) -> None :
15
+ if self .level_set < val :
16
+ return
18
17
19
- if prompt and os .path .exists (output_file ):
20
- result = input (f"File '{ output_file } ' already exists. Overwrite? [y/N] " )
21
- if result .lower () != "y" :
22
- sys .stderr .write ("Not overwriting - exiting\n " )
23
- sys .exit (1 )
18
+ if val == 24 :
19
+ sys .stderr .write (f"\033 [38;5;226;40m{ msg } \033 [0m\n " )
20
+ elif val == 16 :
21
+ sys .stderr .write (f"\033 [38;5;208;40m{ msg } \033 [0m\n " )
22
+ elif val == 8 :
23
+ sys .stderr .write (f"\033 [31;40m{ msg } \033 [0m\n " )
24
+ else :
25
+ sys .stderr .write (f"{ msg } \n " )
24
26
25
- output_container = av .open (output_file , "w" )
26
27
28
+ def transcode (
29
+ log : AvLog ,
30
+ vn : bool ,
31
+ an : bool ,
32
+ sn : bool ,
33
+ input_container : InputContainer ,
34
+ output_container : OutputContainer ,
35
+ ) -> None :
27
36
stream_mapping : dict [int , av .stream .Stream ] = {}
28
37
for stream in input_container .streams :
29
38
if (
@@ -48,13 +57,50 @@ def remux(
48
57
output_container .close ()
49
58
50
59
51
- def parse_args (args : list [str ]) -> None :
60
+ def parse_loglevel (args : list [str ]) -> tuple [int , bool ]:
61
+ log_level = 32
62
+ hide_banner = False
63
+
64
+ def parse_level (v : str ) -> int :
65
+ if v in ("quiet" , "-8" ):
66
+ return - 8
67
+ if v in ("panic" , "0" ):
68
+ return 0
69
+ if v in ("fatal" , "8" ):
70
+ return 8
71
+ if v in ("error" , "16" ):
72
+ return 16
73
+ if v in ("warning" , "24" ):
74
+ return 24
75
+ if v in ("info" , "32" ):
76
+ return 32
77
+ if v in ("verbose" , "40" ):
78
+ return 40
79
+ if v in ("debug" , "48" ):
80
+ return 48
81
+ if v in ("trace" , "56" ):
82
+ return 56
83
+ return 0
84
+
85
+ i = 0
86
+ while i < len (args ):
87
+ arg = args [i ]
88
+ if arg == "-hide_banner" :
89
+ hide_banner = True
90
+ elif arg == "-loglevel" :
91
+ i += 1
92
+ log_level = parse_level (args [i ])
93
+ i += 1
94
+
95
+ return log_level , hide_banner
96
+
97
+
98
+ def parse_args (log : AvLog , hide_banner : bool , args : list [str ]) -> None :
52
99
inputs = []
53
100
outputs = []
54
101
55
102
print_bf = False
56
- print_banner = True
57
- prompt = True
103
+ overwrite = None
58
104
vn = False
59
105
an = False
60
106
sn = False
@@ -64,9 +110,14 @@ def parse_args(args: list[str]) -> None:
64
110
arg = args [i ]
65
111
66
112
if arg == "-y" :
67
- prompt = False
113
+ overwrite = True
114
+ elif arg == "-n" :
115
+ overwrite = False
68
116
elif arg == "-hide_banner" :
69
- print_banner = False
117
+ pass
118
+ elif arg == "-log_level" :
119
+ i += 1
120
+ pass
70
121
elif arg == "-vn" :
71
122
vn = True
72
123
elif arg == "-an" :
@@ -83,7 +134,7 @@ def parse_args(args: list[str]) -> None:
83
134
outputs .append (arg )
84
135
i += 1
85
136
86
- if print_banner :
137
+ if not hide_banner :
87
138
by_config = {}
88
139
for libname , config in sorted (av ._core .library_meta .items ()):
89
140
version = config ["version" ]
@@ -106,20 +157,50 @@ def parse_args(args: list[str]) -> None:
106
157
print ("Bitstream filters:" )
107
158
return
108
159
160
+ if not outputs and not inputs :
161
+ sys .stderr .write (
162
+ "Universal media converter\n usage: ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}...\n \n "
163
+ )
164
+ log .level (24 , "Use -h to get full help or, even better, run 'man ffmpeg'" )
165
+
109
166
if not outputs :
110
167
if args :
111
- sys . stderr . write ( "At least one output file must be specified\n " )
168
+ log . level ( 16 , "At least one output file must be specified" )
112
169
sys .exit (1 )
113
170
114
171
if not inputs :
115
- sys . stderr . write ( "Output file does not contain any stream\n " )
172
+ log . level ( 8 , "Output file does not contain any stream" )
116
173
sys .exit (1 )
117
174
118
- remux (prompt , vn , an , sn , inputs [0 ], outputs [0 ])
175
+ input_containers = []
176
+ for input_file in inputs :
177
+ try :
178
+ input_containers .append (av .open (input_file , "r" ))
179
+ except av .error .FileNotFoundError :
180
+ log .level (8 , f"Error opening input file { input_file } ." )
181
+ sys .exit (1 )
182
+
183
+ output_containers = []
184
+ for output_file in outputs :
185
+ if os .path .exists (output_file ):
186
+ if overwrite is None :
187
+ res = input (f"File '{ output_file } ' already exists. Overwrite? [y/N] " )
188
+ if res .lower () != "y" :
189
+ log .level (16 , "Not overwriting - exiting" )
190
+ sys .exit (1 )
191
+ elif not overwrite :
192
+ log .level (16 , f"File '{ output_file } ' already exists. Exiting." )
193
+ log .level (8 , f"Error opening output file { output_file } ." )
194
+ sys .exit (1 )
195
+
196
+ output_containers .append (av .open (output_file , "w" ))
197
+
198
+ transcode (log , vn , an , sn , input_containers [0 ], output_containers [0 ])
119
199
120
200
121
201
def main (sys_args : list [str ] = sys .argv [1 :]) -> None :
122
- parse_args (sys_args )
202
+ log_level , hide_banner = parse_loglevel (sys_args )
203
+ parse_args (AvLog (log_level ), hide_banner , sys_args )
123
204
124
205
125
206
if __name__ == "__main__" :
0 commit comments