kapsikkum-unmanic – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | #!/usr/bin/env python3 |
2 | # -*- coding: utf-8 -*- |
||
3 | |||
4 | """ |
||
5 | unmanic.main.py |
||
6 | |||
7 | Written by: Josh.5 <jsunnex@gmail.com> |
||
8 | Date: 21 Sep 2019, (2:08 PM) |
||
9 | |||
10 | Copyright: |
||
11 | Copyright (C) Josh Sunnex - All Rights Reserved |
||
12 | |||
13 | Permission is hereby granted, free of charge, to any person obtaining a copy |
||
14 | of this software and associated documentation files (the "Software"), to deal |
||
15 | in the Software without restriction, including without limitation the rights |
||
16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||
17 | copies of the Software, and to permit persons to whom the Software is |
||
18 | furnished to do so, subject to the following conditions: |
||
19 | |||
20 | The above copyright notice and this permission notice shall be included in all |
||
21 | copies or substantial portions of the Software. |
||
22 | |||
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
||
26 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
||
27 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
||
28 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE |
||
29 | OR OTHER DEALINGS IN THE SOFTWARE. |
||
30 | |||
31 | """ |
||
32 | |||
33 | from . import audio_codecs |
||
34 | from . import subtitle_codecs |
||
35 | from . import video_codecs |
||
36 | from .lib import cli |
||
37 | |||
38 | |||
39 | class Info(object): |
||
40 | """ |
||
41 | Info |
||
42 | |||
43 | Provide information on FFMPEG commands and configuration |
||
44 | """ |
||
45 | available_encoders = None |
||
46 | available_decoders = None |
||
47 | |||
48 | @staticmethod |
||
49 | def versions(): |
||
50 | """ |
||
51 | Return the system ffmpeg version as a string |
||
52 | |||
53 | :return: |
||
54 | """ |
||
55 | return cli.ffmpeg_version_info() |
||
56 | |||
57 | def file_probe(self, vid_file_path): |
||
58 | """ |
||
59 | Probe media file and return result dictionary |
||
60 | |||
61 | :param vid_file_path: |
||
62 | :return: |
||
63 | """ |
||
64 | # TODO: Move this to a new "Probe" class |
||
65 | return cli.ffprobe_file(vid_file_path) |
||
66 | |||
67 | def get_available_ffmpeg_encoders(self): |
||
68 | """ |
||
69 | Sets a dictionary of encoders supported by ffmpeg |
||
70 | """ |
||
71 | available_audio_encoders = {} |
||
72 | available_subtitle_encoders = {} |
||
73 | available_video_encoders = {} |
||
74 | |||
75 | # Get raw ffmpeg output of available encoders |
||
76 | info = cli.ffmpeg_available_encoders() |
||
77 | |||
78 | # Sort through the lines and create a dictionary of audio, subtitle and video encoders |
||
79 | for line in info.splitlines(): |
||
80 | line = line.rstrip().lstrip() |
||
81 | if line.startswith('A') and line != 'A..... = Audio': |
||
82 | # Audio encoder |
||
83 | data = line.split() |
||
84 | capabilities = data.pop(0) |
||
85 | codec = data.pop(0) |
||
86 | available_audio_encoders[codec] = { |
||
87 | 'capabilities': capabilities, |
||
88 | 'description': " ".join(data) |
||
89 | } |
||
90 | elif line.startswith('S') and line != 'S..... = Subtitle': |
||
91 | # Subtitle encoder |
||
92 | data = line.split() |
||
93 | capabilities = data.pop(0) |
||
94 | codec = data.pop(0) |
||
95 | available_subtitle_encoders[codec] = { |
||
96 | 'capabilities': capabilities, |
||
97 | 'description': " ".join(data) |
||
98 | } |
||
99 | elif line.startswith('V') and line != 'V..... = Video': |
||
100 | # Video encoder |
||
101 | data = line.split() |
||
102 | capabilities = data.pop(0) |
||
103 | codec = data.pop(0) |
||
104 | available_video_encoders[codec] = { |
||
105 | 'capabilities': capabilities, |
||
106 | 'description': " ".join(data) |
||
107 | } |
||
108 | |||
109 | # Combine dictionaries into one |
||
110 | self.available_encoders = { |
||
111 | 'audio': available_audio_encoders, |
||
112 | 'subtitle': available_subtitle_encoders, |
||
113 | 'video': available_video_encoders |
||
114 | } |
||
115 | |||
116 | return self.available_encoders |
||
117 | |||
118 | def get_available_ffmpeg_decoders(self): |
||
119 | """ |
||
120 | Sets a dictionary of decoders supported by ffmpeg |
||
121 | """ |
||
122 | available_audio_decoders = {} |
||
123 | available_subtitle_decoders = {} |
||
124 | available_video_decoders = {} |
||
125 | |||
126 | # Get raw ffmpeg output of available decoders |
||
127 | info = cli.ffmpeg_available_decoders() |
||
128 | |||
129 | # Sort through the lines and create a dictionary of audio, subtitle and video decoders |
||
130 | for line in info.splitlines(): |
||
131 | line = line.rstrip().lstrip() |
||
132 | if line.startswith('A') and line != 'A..... = Audio': |
||
133 | # Audio decoder |
||
134 | data = line.split() |
||
135 | capabilities = data.pop(0) |
||
136 | codec = data.pop(0) |
||
137 | available_audio_decoders[codec] = { |
||
138 | 'capabilities': capabilities, |
||
139 | 'description': " ".join(data) |
||
140 | } |
||
141 | elif line.startswith('S') and line != 'S..... = Subtitle': |
||
142 | # Subtitle decoder |
||
143 | data = line.split() |
||
144 | capabilities = data.pop(0) |
||
145 | codec = data.pop(0) |
||
146 | available_subtitle_decoders[codec] = { |
||
147 | 'capabilities': capabilities, |
||
148 | 'description': " ".join(data) |
||
149 | } |
||
150 | elif line.startswith('V') and line != 'V..... = Video': |
||
151 | # Video decoder |
||
152 | data = line.split() |
||
153 | capabilities = data.pop(0) |
||
154 | codec = data.pop(0) |
||
155 | available_video_decoders[codec] = { |
||
156 | 'capabilities': capabilities, |
||
157 | 'description': " ".join(data) |
||
158 | } |
||
159 | |||
160 | # Combine dictionaries into one |
||
161 | self.available_decoders = { |
||
162 | 'audio': available_audio_decoders, |
||
163 | 'subtitle': available_subtitle_decoders, |
||
164 | 'video': available_video_decoders |
||
165 | } |
||
166 | |||
167 | return self.available_decoders |
||
168 | |||
169 | def get_available_ffmpeg_hw_acceleration_methods(self): |
||
170 | methods = [] |
||
171 | |||
172 | # Get raw ffmpeg output of available encoders |
||
173 | info = cli.ffmpeg_available_hw_acceleration_methods() |
||
174 | |||
175 | # Sort through the lines and create a list of methods |
||
176 | for line in info.splitlines(): |
||
177 | line = line.rstrip().lstrip() |
||
178 | if not line or line.startswith('Hardware acceleration'): |
||
179 | continue |
||
180 | else: |
||
181 | methods.append(line) |
||
182 | |||
183 | return methods |
||
184 | |||
185 | def get_ffmpeg_audio_encoders(self): |
||
186 | """ |
||
187 | Fetch all audio encoders supported by ffmpeg |
||
188 | |||
189 | :return: |
||
190 | """ |
||
191 | if self.available_encoders is None: |
||
192 | self.get_available_ffmpeg_encoders() |
||
193 | return self.available_encoders['audio'] |
||
194 | |||
195 | def get_ffmpeg_subtitle_encoders(self): |
||
196 | """ |
||
197 | Fetch all subtitle encoders supported by ffmpeg |
||
198 | |||
199 | :return: |
||
200 | """ |
||
201 | if self.available_encoders is None: |
||
202 | self.get_available_ffmpeg_encoders() |
||
203 | return self.available_encoders['subtitle'] |
||
204 | |||
205 | def get_ffmpeg_video_encoders(self): |
||
206 | """ |
||
207 | Fetch all video encoders supported by ffmpeg |
||
208 | |||
209 | :return: |
||
210 | """ |
||
211 | if self.available_encoders is None: |
||
212 | self.get_available_ffmpeg_encoders() |
||
213 | return self.available_encoders['video'] |
||
214 | |||
215 | def filter_available_encoders_for_codec(self, codec_encoders, codec_type): |
||
216 | """ |
||
217 | Filter a given list of encoders. Removes any that are not available with FFMPEG |
||
218 | |||
219 | :param codec_type: |
||
220 | :param codec_encoders: |
||
221 | :return: |
||
222 | """ |
||
223 | available_encoders = {} |
||
224 | if codec_type == 'audio': |
||
225 | available_encoders = self.get_ffmpeg_audio_encoders() |
||
226 | elif codec_type == 'subtitle': |
||
227 | available_encoders = self.get_ffmpeg_subtitle_encoders() |
||
228 | elif codec_type == 'video': |
||
229 | available_encoders = self.get_ffmpeg_video_encoders() |
||
230 | # Iterate through the list of encoders. |
||
231 | for encoder in codec_encoders: |
||
232 | # Check if ffmpeg has that encoder |
||
233 | if encoder not in available_encoders: |
||
234 | # Encoder is not available, remove it from the list |
||
235 | codec_encoders.remove(encoder) |
||
236 | return codec_encoders |
||
237 | |||
238 | def get_all_supported_codecs_of_type(self, codec_type): |
||
239 | """ |
||
240 | Fetch a list of supported codecs and |
||
241 | return a dictionary of their data |
||
242 | |||
243 | :return: |
||
244 | """ |
||
245 | codec_dict = {} |
||
246 | return_codec_dict = {} |
||
247 | if codec_type == 'audio': |
||
248 | codec_dict = audio_codecs.get_all_audio_codecs() |
||
249 | elif codec_type == 'subtitle': |
||
250 | codec_dict = audio_codecs.get_all_audio_codecs() |
||
251 | elif codec_type == 'video': |
||
252 | codec_dict = video_codecs.get_all_video_codecs() |
||
253 | # Iterate through the list of codecs. |
||
254 | for codec_name in codec_dict: |
||
255 | codec = codec_dict[codec_name] |
||
256 | # Get list of encoders for this codec that are available in ffmpeg |
||
257 | codec_encoders = self.filter_available_encoders_for_codec(codec['encoders'], codec_type) |
||
258 | # Check if any encoders were found |
||
259 | if not codec_encoders: |
||
260 | continue |
||
261 | # At least one encoder is found for that codec. |
||
262 | # Add codec to codec_list if one encoder exists |
||
263 | return_codec_dict[codec_name] = codec |
||
264 | return return_codec_dict |
||
265 | |||
266 | def get_all_supported_video_codecs(self): |
||
267 | """ |
||
268 | Fetch a list of supported video codecs and |
||
269 | return a dictionary of their data |
||
270 | |||
271 | :return: |
||
272 | """ |
||
273 | return_codec_dict = {} |
||
274 | codec_dict = video_codecs.get_all_video_codecs() |
||
275 | for codec_name in codec_dict: |
||
276 | codec = codec_dict[codec_name] |
||
277 | # Get list of encoders for this codec that are available in ffmpeg |
||
278 | codec_encoders = self.filter_available_encoders_for_codec(codec['encoders'], 'video') |
||
279 | # Check if any encoders were found |
||
280 | if not codec_encoders: |
||
281 | continue |
||
282 | # At least one encoder is found for that codec. |
||
283 | # Add codec to codec_list if one encoder exists |
||
284 | return_codec_dict[codec_name] = codec |
||
285 | return return_codec_dict |
||
286 | |||
287 | def get_all_supported_codecs(self): |
||
288 | supported_audio_codecs = self.get_all_supported_codecs_of_type('audio') |
||
289 | # TODO: Subtitles |
||
290 | supported_video_codecs = self.get_all_supported_codecs_of_type('video') |
||
291 | |||
292 | # Combine dictionaries into one and return |
||
293 | return { |
||
294 | 'audio': supported_audio_codecs, |
||
295 | 'subtitle': {}, |
||
296 | 'video': supported_video_codecs |
||
297 | } |