helpers.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  1. # Kudos to Werner Robitza, AVEQ GmbH, for helping with ffmpeg
  2. # related content
  3. import hashlib
  4. import json
  5. import math
  6. import os
  7. import random
  8. import shutil
  9. import subprocess
  10. import tempfile
  11. from fractions import Fraction
  12. import filetype
  13. from django.conf import settings
  14. CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  15. CRF_ENCODING_NUM_SECONDS = 2 # 0 * 60 # videos with greater duration will get
  16. # CRF encoding and not two-pass
  17. # Encoding individual chunks may yield quality variations if you use a
  18. # too low bitrate, so if you go for the chunk-based variant
  19. # you should use CRF encoding.
  20. MAX_RATE_MULTIPLIER = 1.5
  21. MIN_RATE_MULTIPLIER = 0.5
  22. BUF_SIZE_MULTIPLIER = 1.5
  23. # in seconds, anything between 2 and 6 makes sense
  24. KEYFRAME_DISTANCE = 4
  25. KEYFRAME_DISTANCE_MIN = 2
  26. # speed presets
  27. # see https://trac.ffmpeg.org/wiki/Encode/H.264
  28. X26x_PRESET = "medium" # "medium"
  29. X265_PRESET = "medium"
  30. X26x_PRESET_BIG_HEIGHT = "faster"
  31. # VP9_SPEED = 1 # between 0 and 4, lower is slower
  32. VP9_SPEED = 2
  33. VIDEO_CRFS = {
  34. "h264_baseline": 23,
  35. "h264": 23,
  36. "h265": 28,
  37. "vp9": 32,
  38. }
  39. # video rates for 25 or 60 fps input, for different codecs, in kbps
  40. VIDEO_BITRATES = {
  41. "h264": {
  42. 25: {
  43. 240: 300,
  44. 360: 500,
  45. 480: 1000,
  46. 720: 2500,
  47. 1080: 4500,
  48. 1440: 9000,
  49. 2160: 18000,
  50. },
  51. 60: {720: 3500, 1080: 7500, 1440: 18000, 2160: 40000},
  52. },
  53. "h265": {
  54. 25: {
  55. 240: 150,
  56. 360: 275,
  57. 480: 500,
  58. 720: 1024,
  59. 1080: 1800,
  60. 1440: 4500,
  61. 2160: 10000,
  62. },
  63. 60: {720: 1800, 1080: 3000, 1440: 8000, 2160: 18000},
  64. },
  65. "vp9": {
  66. 25: {
  67. 240: 150,
  68. 360: 275,
  69. 480: 500,
  70. 720: 1024,
  71. 1080: 1800,
  72. 1440: 4500,
  73. 2160: 10000,
  74. },
  75. 60: {720: 1800, 1080: 3000, 1440: 8000, 2160: 18000},
  76. },
  77. }
  78. AUDIO_ENCODERS = {"h264": "aac", "h265": "aac", "vp9": "libopus"}
  79. AUDIO_BITRATES = {"h264": 128, "h265": 128, "vp9": 96}
  80. EXTENSIONS = {"h264": "mp4", "h265": "mp4", "vp9": "webm"}
  81. VIDEO_PROFILES = {"h264": "main", "h265": "main"}
  82. def get_portal_workflow():
  83. return settings.PORTAL_WORKFLOW
  84. def get_default_state(user=None):
  85. # possible states given the portal workflow setting
  86. state = "private"
  87. if settings.PORTAL_WORKFLOW == "public":
  88. state = "public"
  89. if settings.PORTAL_WORKFLOW == "unlisted":
  90. state = "unlisted"
  91. if settings.PORTAL_WORKFLOW == "private_verified":
  92. if user and user.advancedUser:
  93. state = "unlisted"
  94. return state
  95. def get_file_name(filename):
  96. return filename.split("/")[-1]
  97. def get_file_type(filename):
  98. if not os.path.exists(filename):
  99. return None
  100. file_type = None
  101. kind = filetype.guess(filename)
  102. if kind is not None:
  103. if kind.mime.startswith("video"):
  104. file_type = "video"
  105. elif kind.mime.startswith("image"):
  106. file_type = "image"
  107. elif kind.mime.startswith("audio"):
  108. file_type = "audio"
  109. elif "pdf" in kind.mime:
  110. file_type = "pdf"
  111. else:
  112. # TODO: do something for files not supported by filetype lib
  113. pass
  114. return file_type
  115. def rm_file(filename):
  116. if os.path.isfile(filename):
  117. try:
  118. os.remove(filename)
  119. return True
  120. except OSError:
  121. pass
  122. return False
  123. def rm_files(filenames):
  124. if isinstance(filenames, list):
  125. for filename in filenames:
  126. rm_file(filename)
  127. return True
  128. def rm_dir(directory):
  129. if os.path.isdir(directory):
  130. # refuse to delete a dir inside project BASE_DIR
  131. if directory.startswith(settings.BASE_DIR):
  132. try:
  133. shutil.rmtree(directory)
  134. return True
  135. except (FileNotFoundError, PermissionError):
  136. pass
  137. return False
  138. def url_from_path(filename):
  139. # TODO: find a way to preserver http - https ...
  140. return "{0}{1}".format(settings.MEDIA_URL, filename.replace(settings.MEDIA_ROOT, ""))
  141. def create_temp_file(suffix=None, dir=settings.TEMP_DIRECTORY):
  142. tf = tempfile.NamedTemporaryFile(delete=False, suffix=suffix, dir=dir)
  143. return tf.name
  144. def create_temp_dir(suffix=None, dir=settings.TEMP_DIRECTORY):
  145. td = tempfile.mkdtemp(dir=dir)
  146. return td
  147. def produce_friendly_token(token_len=settings.FRIENDLY_TOKEN_LEN):
  148. token = ""
  149. while len(token) != token_len:
  150. token += CHARS[random.randint(0, len(CHARS) - 1)]
  151. return token
  152. def clean_friendly_token(token):
  153. # cleans token
  154. for char in token:
  155. if char not in CHARS:
  156. token.replace(char, "")
  157. return token
  158. def mask_ip(ip_address):
  159. return hashlib.md5(ip_address.encode("utf-8")).hexdigest()
  160. def run_command(cmd, cwd=None):
  161. """
  162. Run a command directly
  163. """
  164. if isinstance(cmd, str):
  165. cmd = cmd.split()
  166. ret = {}
  167. if cwd:
  168. process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
  169. else:
  170. process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  171. stdout, stderr = process.communicate()
  172. # TODO: catch unicodedecodeerrors here...
  173. if process.returncode == 0:
  174. try:
  175. ret["out"] = stdout.decode("utf-8")
  176. except BaseException:
  177. ret["out"] = ""
  178. try:
  179. ret["error"] = stderr.decode("utf-8")
  180. except BaseException:
  181. ret["error"] = ""
  182. else:
  183. try:
  184. ret["error"] = stderr.decode("utf-8")
  185. except BaseException:
  186. ret["error"] = ""
  187. return ret
  188. def media_file_info(input_file):
  189. """
  190. Get the info about an input file, as determined by ffprobe
  191. Returns a dict, with the keys:
  192. - `filename`: Filename
  193. - `file_size`: Size of the file in bytes
  194. - `video_duration`: Duration of the video in `s.msec`
  195. - `video_frame_rate_d`: Framerate franction denominator
  196. - `video_frame_rate_n`: Framerate fraction nominator
  197. - `video_bitrate`: Bitrate of the video stream in kBit/s
  198. - `video_width`: Width in pixels
  199. - `video_height`: Height in pixels
  200. - `video_codec`: Video codec
  201. - `audio_duration`: Duration of the audio in `s.msec`
  202. - `audio_sample_rate`: Audio sample rate in Hz
  203. - `audio_codec`: Audio codec name (`aac`)
  204. - `audio_bitrate`: Bitrate of the video stream in kBit/s
  205. Also returns the video and audio info raw from ffprobe.
  206. """
  207. ret = {}
  208. if not os.path.isfile(input_file):
  209. ret["fail"] = True
  210. return ret
  211. video_info = {}
  212. audio_info = {}
  213. cmd = ["stat", "-c", "%s", input_file]
  214. stdout = run_command(cmd).get("out")
  215. if stdout:
  216. file_size = int(stdout.strip())
  217. else:
  218. ret["fail"] = True
  219. return ret
  220. cmd = ["md5sum", input_file]
  221. stdout = run_command(cmd).get("out")
  222. if stdout:
  223. md5sum = stdout.split()[0]
  224. else:
  225. md5sum = ""
  226. cmd = [
  227. settings.FFPROBE_COMMAND,
  228. "-loglevel",
  229. "error",
  230. "-show_streams",
  231. "-show_entries",
  232. "format=format_name",
  233. "-of",
  234. "json",
  235. input_file,
  236. ]
  237. stdout = run_command(cmd).get("out")
  238. try:
  239. info = json.loads(stdout)
  240. except TypeError:
  241. ret["fail"] = True
  242. return ret
  243. has_video = False
  244. has_audio = False
  245. for stream_info in info["streams"]:
  246. if stream_info["codec_type"] == "video":
  247. video_info = stream_info
  248. has_video = True
  249. if info.get("format") and info["format"].get("format_name", "") in [
  250. "tty",
  251. "image2",
  252. "image2pipe",
  253. "bin",
  254. "png_pipe",
  255. "gif",
  256. ]:
  257. ret["fail"] = True
  258. return ret
  259. elif stream_info["codec_type"] == "audio":
  260. audio_info = stream_info
  261. has_audio = True
  262. if not has_video:
  263. ret["is_video"] = False
  264. ret["is_audio"] = has_audio
  265. ret["audio_info"] = audio_info
  266. return ret
  267. if "duration" in video_info.keys():
  268. video_duration = float(video_info["duration"])
  269. elif "tags" in video_info.keys() and "DURATION" in video_info["tags"]:
  270. duration_str = video_info["tags"]["DURATION"]
  271. try:
  272. hms, msec = duration_str.split(".")
  273. except ValueError:
  274. hms, msec = duration_str.split(",")
  275. total_dur = sum(int(x) * 60 ** i for i, x in enumerate(reversed(hms.split(":"))))
  276. video_duration = total_dur + float("0." + msec)
  277. else:
  278. # fallback to format, eg for webm
  279. cmd = [
  280. settings.FFPROBE_COMMAND,
  281. "-loglevel",
  282. "error",
  283. "-show_format",
  284. "-of",
  285. "json",
  286. input_file,
  287. ]
  288. stdout = run_command(cmd).get("out")
  289. format_info = json.loads(stdout)["format"]
  290. try:
  291. video_duration = float(format_info["duration"])
  292. except KeyError:
  293. ret["fail"] = True
  294. return ret
  295. if "bit_rate" in video_info.keys():
  296. video_bitrate = round(float(video_info["bit_rate"]) / 1024.0, 2)
  297. else:
  298. cmd = [
  299. settings.FFPROBE_COMMAND,
  300. "-loglevel",
  301. "error",
  302. "-select_streams",
  303. "v",
  304. "-show_entries",
  305. "packet=size",
  306. "-of",
  307. "compact=p=0:nk=1",
  308. input_file,
  309. ]
  310. stdout = run_command(cmd).get("out")
  311. stream_size = sum([int(line) for line in stdout.split("\n") if line != ""])
  312. video_bitrate = round((stream_size * 8 / 1024.0) / video_duration, 2)
  313. if "r_frame_rate" in video_info.keys():
  314. video_frame_rate = video_info["r_frame_rate"].partition("/")
  315. video_frame_rate_n = video_frame_rate[0]
  316. video_frame_rate_d = video_frame_rate[2]
  317. ret = {
  318. "filename": input_file,
  319. "file_size": file_size,
  320. "video_duration": video_duration,
  321. "video_frame_rate_n": video_frame_rate_n,
  322. "video_frame_rate_d": video_frame_rate_d,
  323. "video_bitrate": video_bitrate,
  324. "video_width": video_info["width"],
  325. "video_height": video_info["height"],
  326. "video_codec": video_info["codec_name"],
  327. "has_video": has_video,
  328. "has_audio": has_audio,
  329. "color_range": video_info.get("color_range"),
  330. "color_space": video_info.get("color_space"),
  331. "color_transfer": video_info.get("color_space"),
  332. "color_primaries": video_info.get("color_primaries"),
  333. "field_order": video_info.get("field_order"),
  334. "display_aspect_ratio": video_info.get("display_aspect_ratio"),
  335. "sample_aspect_ratio": video_info.get("sample_aspect_ratio"),
  336. }
  337. if has_audio:
  338. if "duration" in audio_info.keys():
  339. audio_duration = float(audio_info["duration"])
  340. elif "tags" in audio_info.keys() and "DURATION" in audio_info["tags"]:
  341. duration_str = audio_info["tags"]["DURATION"]
  342. try:
  343. hms, msec = duration_str.split(".")
  344. except ValueError:
  345. hms, msec = duration_str.split(",")
  346. total_dur = sum(int(x) * 60 ** i for i, x in enumerate(reversed(hms.split(":"))))
  347. audio_duration = total_dur + float("0." + msec)
  348. else:
  349. # fallback to format, eg for webm
  350. cmd = [
  351. settings.FFPROBE_COMMAND,
  352. "-loglevel",
  353. "error",
  354. "-show_format",
  355. "-of",
  356. "json",
  357. input_file,
  358. ]
  359. stdout = run_command(cmd).get("out")
  360. format_info = json.loads(stdout)["format"]
  361. audio_duration = float(format_info["duration"])
  362. if "bit_rate" in audio_info.keys():
  363. audio_bitrate = round(float(audio_info["bit_rate"]) / 1024.0, 2)
  364. else:
  365. # fall back to calculating from accumulated frame duration
  366. cmd = [
  367. settings.FFPROBE_COMMAND,
  368. "-loglevel",
  369. "error",
  370. "-select_streams",
  371. "a",
  372. "-show_entries",
  373. "packet=size",
  374. "-of",
  375. "compact=p=0:nk=1",
  376. input_file,
  377. ]
  378. stdout = run_command(cmd).get("out")
  379. stream_size = sum([int(line) for line in stdout.split("\n") if line != ""])
  380. audio_bitrate = round((stream_size * 8 / 1024.0) / audio_duration, 2)
  381. ret.update(
  382. {
  383. "audio_duration": audio_duration,
  384. "audio_sample_rate": audio_info["sample_rate"],
  385. "audio_codec": audio_info["codec_name"],
  386. "audio_bitrate": audio_bitrate,
  387. "audio_channels": audio_info["channels"],
  388. }
  389. )
  390. ret["video_info"] = video_info
  391. ret["audio_info"] = audio_info
  392. ret["is_video"] = True
  393. ret["md5sum"] = md5sum
  394. return ret
  395. def calculate_seconds(duration):
  396. # returns seconds, given a ffmpeg extracted string
  397. ret = 0
  398. if isinstance(duration, str):
  399. duration = duration.split(":")
  400. if len(duration) != 3:
  401. return ret
  402. else:
  403. return ret
  404. ret += int(float(duration[2]))
  405. ret += int(float(duration[1])) * 60
  406. ret += int(float(duration[0])) * 60 * 60
  407. return ret
  408. def show_file_size(size):
  409. if size:
  410. size = size / 1000000
  411. size = round(size, 1)
  412. size = "{0}MB".format(str(size))
  413. return size
  414. def get_base_ffmpeg_command(
  415. input_file,
  416. output_file,
  417. has_audio,
  418. codec,
  419. encoder,
  420. audio_encoder,
  421. target_fps,
  422. target_height,
  423. target_rate,
  424. target_rate_audio,
  425. pass_file,
  426. pass_number,
  427. enc_type,
  428. chunk,
  429. ):
  430. """Get the base command for a specific codec, height/rate, and pass
  431. Arguments:
  432. input_file {str} -- input file name
  433. output_file {str} -- output file name
  434. has_audio {bool} -- does the input have audio?
  435. codec {str} -- video codec
  436. encoder {str} -- video encoder
  437. audio_encoder {str} -- audio encoder
  438. target_fps {fractions.Fraction} -- target FPS
  439. target_height {int} -- height
  440. target_rate {int} -- target bitrate in kbps
  441. target_rate_audio {int} -- audio target bitrate
  442. pass_file {str} -- path to temp pass file
  443. pass_number {int} -- number of passes
  444. enc_type {str} -- encoding type (twopass or crf)
  445. """
  446. # avoid very high frame rates
  447. while target_fps > 60:
  448. target_fps = target_fps/2
  449. if target_fps < 1:
  450. target_fps = 1
  451. base_cmd = [
  452. settings.FFMPEG_COMMAND,
  453. "-y",
  454. "-i",
  455. input_file,
  456. "-c:v",
  457. encoder,
  458. "-filter:v",
  459. "scale=-2:" + str(target_height) + ",fps=fps=" + str(target_fps),
  460. # always convert to 4:2:0 -- FIXME: this could be also 4:2:2
  461. # but compatibility will suffer
  462. "-pix_fmt",
  463. "yuv420p",
  464. ]
  465. if enc_type == "twopass":
  466. base_cmd.extend(["-b:v", str(target_rate) + "k"])
  467. elif enc_type == "crf":
  468. base_cmd.extend(["-crf", str(VIDEO_CRFS[codec])])
  469. if encoder == "libvpx-vp9":
  470. base_cmd.extend(["-b:v", str(target_rate) + "k"])
  471. if has_audio:
  472. base_cmd.extend(
  473. [
  474. "-c:a",
  475. audio_encoder,
  476. "-b:a",
  477. str(target_rate_audio) + "k",
  478. # stereo audio only, see https://trac.ffmpeg.org/ticket/5718
  479. "-ac",
  480. "2",
  481. ]
  482. )
  483. # get keyframe distance in frames
  484. keyframe_distance = int(target_fps * KEYFRAME_DISTANCE)
  485. # start building the command
  486. cmd = base_cmd[:]
  487. # preset settings
  488. if encoder == "libvpx-vp9":
  489. if pass_number == 1:
  490. speed = 4
  491. else:
  492. speed = VP9_SPEED
  493. elif encoder in ["libx264"]:
  494. preset = X26x_PRESET
  495. elif encoder in ["libx265"]:
  496. preset = X265_PRESET
  497. if target_height >= 720:
  498. preset = X26x_PRESET_BIG_HEIGHT
  499. if encoder == "libx264":
  500. level = "4.2" if target_height <= 1080 else "5.2"
  501. x264_params = [
  502. "keyint=" + str(keyframe_distance * 2),
  503. "keyint_min=" + str(keyframe_distance),
  504. ]
  505. cmd.extend(
  506. [
  507. "-maxrate",
  508. str(int(int(target_rate) * MAX_RATE_MULTIPLIER)) + "k",
  509. "-bufsize",
  510. str(int(int(target_rate) * BUF_SIZE_MULTIPLIER)) + "k",
  511. "-force_key_frames",
  512. "expr:gte(t,n_forced*" + str(KEYFRAME_DISTANCE) + ")",
  513. "-x264-params",
  514. ":".join(x264_params),
  515. "-preset",
  516. preset,
  517. "-profile:v",
  518. VIDEO_PROFILES[codec],
  519. "-level",
  520. level,
  521. ]
  522. )
  523. if enc_type == "twopass":
  524. cmd.extend(["-passlogfile", pass_file, "-pass", pass_number])
  525. elif encoder == "libx265":
  526. x265_params = [
  527. "vbv-maxrate=" + str(int(int(target_rate) * MAX_RATE_MULTIPLIER)),
  528. "vbv-bufsize=" + str(int(int(target_rate) * BUF_SIZE_MULTIPLIER)),
  529. "keyint=" + str(keyframe_distance * 2),
  530. "keyint_min=" + str(keyframe_distance),
  531. ]
  532. if enc_type == "twopass":
  533. x265_params.extend(["stats=" + str(pass_file), "pass=" + str(pass_number)])
  534. cmd.extend(
  535. [
  536. "-force_key_frames",
  537. "expr:gte(t,n_forced*" + str(KEYFRAME_DISTANCE) + ")",
  538. "-x265-params",
  539. ":".join(x265_params),
  540. "-preset",
  541. preset,
  542. "-profile:v",
  543. VIDEO_PROFILES[codec],
  544. ]
  545. )
  546. elif encoder == "libvpx-vp9":
  547. cmd.extend(
  548. [
  549. "-g",
  550. str(keyframe_distance),
  551. "-keyint_min",
  552. str(keyframe_distance),
  553. "-maxrate",
  554. str(int(int(target_rate) * MAX_RATE_MULTIPLIER)) + "k",
  555. "-minrate",
  556. str(int(int(target_rate) * MIN_RATE_MULTIPLIER)) + "k",
  557. "-bufsize",
  558. str(int(int(target_rate) * BUF_SIZE_MULTIPLIER)) + "k",
  559. "-speed",
  560. speed,
  561. # '-deadline', 'realtime',
  562. ]
  563. )
  564. if enc_type == "twopass":
  565. cmd.extend(["-passlogfile", pass_file, "-pass", pass_number])
  566. cmd.extend(
  567. [
  568. "-strict",
  569. "-2",
  570. ]
  571. )
  572. # end of the command
  573. if pass_number == 1:
  574. cmd.extend(["-an", "-f", "null", "/dev/null"])
  575. elif pass_number == 2:
  576. if output_file.endswith("mp4") and chunk:
  577. cmd.extend(["-movflags", "+faststart"])
  578. cmd.extend([output_file])
  579. return cmd
  580. def produce_ffmpeg_commands(media_file, media_info, resolution, codec, output_filename, pass_file, chunk=False):
  581. try:
  582. media_info = json.loads(media_info)
  583. except BaseException:
  584. media_info = {}
  585. if codec == "h264":
  586. encoder = "libx264"
  587. # ext = "mp4"
  588. elif codec in ["h265", "hevc"]:
  589. encoder = "libx265"
  590. # ext = "mp4"
  591. elif codec == "vp9":
  592. encoder = "libvpx-vp9"
  593. # ext = "webm"
  594. else:
  595. return False
  596. target_fps = Fraction(int(media_info.get("video_frame_rate_n", 30)), int(media_info.get("video_frame_rate_d", 1)))
  597. if target_fps <= 30:
  598. target_rate = VIDEO_BITRATES[codec][25].get(resolution)
  599. else:
  600. target_rate = VIDEO_BITRATES[codec][60].get(resolution)
  601. if not target_rate: # INVESTIGATE MORE!
  602. target_rate = VIDEO_BITRATES[codec][25].get(resolution)
  603. if not target_rate:
  604. return False
  605. if media_info.get("video_height") < resolution:
  606. if resolution not in [240, 360]: # always get these two
  607. return False
  608. # if codec == "h264_baseline":
  609. # target_fps = 25
  610. # else:
  611. if media_info.get("video_duration") > CRF_ENCODING_NUM_SECONDS:
  612. enc_type = "crf"
  613. else:
  614. enc_type = "twopass"
  615. if enc_type == "twopass":
  616. passes = [1, 2]
  617. elif enc_type == "crf":
  618. passes = [2]
  619. cmds = []
  620. for pass_number in passes:
  621. cmds.append(
  622. get_base_ffmpeg_command(
  623. media_file,
  624. output_file=output_filename,
  625. has_audio=media_info.get("has_audio"),
  626. codec=codec,
  627. encoder=encoder,
  628. audio_encoder=AUDIO_ENCODERS[codec],
  629. target_fps=target_fps,
  630. target_height=resolution,
  631. target_rate=target_rate,
  632. target_rate_audio=AUDIO_BITRATES[codec],
  633. pass_file=pass_file,
  634. pass_number=pass_number,
  635. enc_type=enc_type,
  636. chunk=chunk,
  637. )
  638. )
  639. return cmds
  640. def clean_query(query):
  641. """This is used to clear text in order to comply with SearchQuery
  642. known exception cases
  643. :param query: str - the query text that we want to clean
  644. :return:
  645. """
  646. if not query:
  647. return ""
  648. chars = ["^", "{", "}", "&", "|", "<", ">", '"', ")", "(", "!", ":", ";", "'", "#"]
  649. for char in chars:
  650. query = query.replace(char, "")
  651. return query.lower()