helpers.py 21 KB

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