helpers.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795
  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. - `interlaced` : True if the video is interlaced
  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. interlaced = False
  318. if video_info.get("field_order") in ("tt", "tb", "bt", "bb"):
  319. interlaced = True
  320. ret = {
  321. "filename": input_file,
  322. "file_size": file_size,
  323. "video_duration": video_duration,
  324. "video_frame_rate_n": video_frame_rate_n,
  325. "video_frame_rate_d": video_frame_rate_d,
  326. "video_bitrate": video_bitrate,
  327. "video_width": video_info["width"],
  328. "video_height": video_info["height"],
  329. "video_codec": video_info["codec_name"],
  330. "has_video": has_video,
  331. "has_audio": has_audio,
  332. "color_range": video_info.get("color_range"),
  333. "color_space": video_info.get("color_space"),
  334. "color_transfer": video_info.get("color_space"),
  335. "color_primaries": video_info.get("color_primaries"),
  336. "interlaced": interlaced,
  337. "display_aspect_ratio": video_info.get("display_aspect_ratio"),
  338. "sample_aspect_ratio": video_info.get("sample_aspect_ratio"),
  339. }
  340. if has_audio:
  341. if "duration" in audio_info.keys():
  342. audio_duration = float(audio_info["duration"])
  343. elif "tags" in audio_info.keys() and "DURATION" in audio_info["tags"]:
  344. duration_str = audio_info["tags"]["DURATION"]
  345. try:
  346. hms, msec = duration_str.split(".")
  347. except ValueError:
  348. hms, msec = duration_str.split(",")
  349. total_dur = sum(int(x) * 60**i for i, x in enumerate(reversed(hms.split(":"))))
  350. audio_duration = total_dur + float("0." + msec)
  351. else:
  352. # fallback to format, eg for webm
  353. cmd = [
  354. settings.FFPROBE_COMMAND,
  355. "-loglevel",
  356. "error",
  357. "-show_format",
  358. "-of",
  359. "json",
  360. input_file,
  361. ]
  362. stdout = run_command(cmd).get("out")
  363. format_info = json.loads(stdout)["format"]
  364. audio_duration = float(format_info["duration"])
  365. if "bit_rate" in audio_info.keys():
  366. audio_bitrate = round(float(audio_info["bit_rate"]) / 1024.0, 2)
  367. else:
  368. # fall back to calculating from accumulated frame duration
  369. cmd = [
  370. settings.FFPROBE_COMMAND,
  371. "-loglevel",
  372. "error",
  373. "-select_streams",
  374. "a",
  375. "-show_entries",
  376. "packet=size",
  377. "-of",
  378. "compact=p=0:nk=1",
  379. input_file,
  380. ]
  381. stdout = run_command(cmd).get("out")
  382. # ffprobe appends a pipe at the end of the output, thus we have to remove it
  383. stream_size = sum([int(line.replace("|", "")) for line in stdout.split("\n") if line != ""])
  384. audio_bitrate = round((stream_size * 8 / 1024.0) / audio_duration, 2)
  385. ret.update(
  386. {
  387. "audio_duration": audio_duration,
  388. "audio_sample_rate": audio_info["sample_rate"],
  389. "audio_codec": audio_info["codec_name"],
  390. "audio_bitrate": audio_bitrate,
  391. "audio_channels": audio_info["channels"],
  392. }
  393. )
  394. ret["video_info"] = video_info
  395. ret["audio_info"] = audio_info
  396. ret["is_video"] = True
  397. ret["md5sum"] = md5sum
  398. return ret
  399. def calculate_seconds(duration):
  400. # returns seconds, given a ffmpeg extracted string
  401. ret = 0
  402. if isinstance(duration, str):
  403. duration = duration.split(":")
  404. if len(duration) != 3:
  405. return ret
  406. else:
  407. return ret
  408. ret += int(float(duration[2]))
  409. ret += int(float(duration[1])) * 60
  410. ret += int(float(duration[0])) * 60 * 60
  411. return ret
  412. def show_file_size(size):
  413. if size:
  414. size = size / 1000000
  415. size = round(size, 1)
  416. size = "{0}MB".format(str(size))
  417. return size
  418. def get_base_ffmpeg_command(
  419. input_file,
  420. output_file,
  421. has_audio,
  422. codec,
  423. encoder,
  424. audio_encoder,
  425. target_fps,
  426. interlaced,
  427. target_height,
  428. target_rate,
  429. target_rate_audio,
  430. pass_file,
  431. pass_number,
  432. enc_type,
  433. chunk,
  434. ):
  435. """Get the base command for a specific codec, height/rate, and pass
  436. Arguments:
  437. input_file {str} -- input file name
  438. output_file {str} -- output file name
  439. has_audio {bool} -- does the input have audio?
  440. codec {str} -- video codec
  441. encoder {str} -- video encoder
  442. audio_encoder {str} -- audio encoder
  443. target_fps {fractions.Fraction} -- target FPS
  444. interlaced {bool} -- true if interlaced
  445. target_height {int} -- height
  446. target_rate {int} -- target bitrate in kbps
  447. target_rate_audio {int} -- audio target bitrate
  448. pass_file {str} -- path to temp pass file
  449. pass_number {int} -- number of passes
  450. enc_type {str} -- encoding type (twopass or crf)
  451. """
  452. # avoid very high frame rates
  453. while target_fps > 60:
  454. target_fps = target_fps / 2
  455. if target_fps < 1:
  456. target_fps = 1
  457. filters = []
  458. if interlaced:
  459. filters.append("yadif")
  460. target_width = round(target_height * 16 / 9)
  461. scale_filter_opts = [
  462. f"if(lt(iw\\,ih)\\,{target_height}\\,{target_width})", # noqa
  463. f"if(lt(iw\\,ih)\\,{target_width}\\,{target_height})", # noqa
  464. "force_original_aspect_ratio=decrease",
  465. "force_divisible_by=2",
  466. "flags=lanczos",
  467. ]
  468. scale_filter_str = "scale=" + ":".join(scale_filter_opts)
  469. filters.append(scale_filter_str)
  470. fps_str = f"fps=fps={target_fps}"
  471. filters.append(fps_str)
  472. filters_str = ",".join(filters)
  473. base_cmd = [
  474. settings.FFMPEG_COMMAND,
  475. "-y",
  476. "-i",
  477. input_file,
  478. "-c:v",
  479. encoder,
  480. "-filter:v",
  481. filters_str,
  482. "-pix_fmt",
  483. "yuv420p",
  484. ]
  485. if enc_type == "twopass":
  486. base_cmd.extend(["-b:v", str(target_rate) + "k"])
  487. elif enc_type == "crf":
  488. base_cmd.extend(["-crf", str(VIDEO_CRFS[codec])])
  489. if encoder == "libvpx-vp9":
  490. base_cmd.extend(["-b:v", str(target_rate) + "k"])
  491. if has_audio:
  492. base_cmd.extend(
  493. [
  494. "-c:a",
  495. audio_encoder,
  496. "-b:a",
  497. str(target_rate_audio) + "k",
  498. # stereo audio only, see https://trac.ffmpeg.org/ticket/5718
  499. "-ac",
  500. "2",
  501. ]
  502. )
  503. # get keyframe distance in frames
  504. keyframe_distance = int(target_fps * KEYFRAME_DISTANCE)
  505. # start building the command
  506. cmd = base_cmd[:]
  507. # preset settings
  508. if encoder == "libvpx-vp9":
  509. if pass_number == 1:
  510. speed = 4
  511. else:
  512. speed = VP9_SPEED
  513. elif encoder in ["libx264"]:
  514. preset = X26x_PRESET
  515. elif encoder in ["libx265"]:
  516. preset = X265_PRESET
  517. if target_height >= 720:
  518. preset = X26x_PRESET_BIG_HEIGHT
  519. if encoder == "libx264":
  520. level = "4.2" if target_height <= 1080 else "5.2"
  521. x264_params = [
  522. "keyint=" + str(keyframe_distance * 2),
  523. "keyint_min=" + str(keyframe_distance),
  524. ]
  525. cmd.extend(
  526. [
  527. "-maxrate",
  528. str(int(int(target_rate) * MAX_RATE_MULTIPLIER)) + "k",
  529. "-bufsize",
  530. str(int(int(target_rate) * BUF_SIZE_MULTIPLIER)) + "k",
  531. "-force_key_frames",
  532. "expr:gte(t,n_forced*" + str(KEYFRAME_DISTANCE) + ")",
  533. "-x264-params",
  534. ":".join(x264_params),
  535. "-preset",
  536. preset,
  537. "-profile:v",
  538. VIDEO_PROFILES[codec],
  539. "-level",
  540. level,
  541. ]
  542. )
  543. if enc_type == "twopass":
  544. cmd.extend(["-passlogfile", pass_file, "-pass", pass_number])
  545. elif encoder == "libx265":
  546. x265_params = [
  547. "vbv-maxrate=" + str(int(int(target_rate) * MAX_RATE_MULTIPLIER)),
  548. "vbv-bufsize=" + str(int(int(target_rate) * BUF_SIZE_MULTIPLIER)),
  549. "keyint=" + str(keyframe_distance * 2),
  550. "keyint_min=" + str(keyframe_distance),
  551. ]
  552. if enc_type == "twopass":
  553. x265_params.extend(["stats=" + str(pass_file), "pass=" + str(pass_number)])
  554. cmd.extend(
  555. [
  556. "-force_key_frames",
  557. "expr:gte(t,n_forced*" + str(KEYFRAME_DISTANCE) + ")",
  558. "-x265-params",
  559. ":".join(x265_params),
  560. "-preset",
  561. preset,
  562. "-profile:v",
  563. VIDEO_PROFILES[codec],
  564. ]
  565. )
  566. elif encoder == "libvpx-vp9":
  567. cmd.extend(
  568. [
  569. "-g",
  570. str(keyframe_distance),
  571. "-keyint_min",
  572. str(keyframe_distance),
  573. "-maxrate",
  574. str(int(int(target_rate) * MAX_RATE_MULTIPLIER)) + "k",
  575. "-minrate",
  576. str(int(int(target_rate) * MIN_RATE_MULTIPLIER)) + "k",
  577. "-bufsize",
  578. str(int(int(target_rate) * BUF_SIZE_MULTIPLIER)) + "k",
  579. "-speed",
  580. speed,
  581. # '-deadline', 'realtime',
  582. ]
  583. )
  584. if enc_type == "twopass":
  585. cmd.extend(["-passlogfile", pass_file, "-pass", pass_number])
  586. cmd.extend(
  587. [
  588. "-strict",
  589. "-2",
  590. ]
  591. )
  592. # end of the command
  593. if pass_number == 1:
  594. cmd.extend(["-an", "-f", "null", "/dev/null"])
  595. elif pass_number == 2:
  596. if output_file.endswith("mp4") and chunk:
  597. cmd.extend(["-movflags", "+faststart"])
  598. cmd.extend([output_file])
  599. return cmd
  600. def produce_ffmpeg_commands(media_file, media_info, resolution, codec, output_filename, pass_file, chunk=False):
  601. try:
  602. media_info = json.loads(media_info)
  603. except BaseException:
  604. media_info = {}
  605. if codec == "h264":
  606. encoder = "libx264"
  607. # ext = "mp4"
  608. elif codec in ["h265", "hevc"]:
  609. encoder = "libx265"
  610. # ext = "mp4"
  611. elif codec == "vp9":
  612. encoder = "libvpx-vp9"
  613. # ext = "webm"
  614. else:
  615. return False
  616. target_fps = Fraction(int(media_info.get("video_frame_rate_n", 30)), int(media_info.get("video_frame_rate_d", 1)))
  617. if target_fps <= 30:
  618. target_rate = VIDEO_BITRATES[codec][25].get(resolution)
  619. else:
  620. target_rate = VIDEO_BITRATES[codec][60].get(resolution)
  621. if not target_rate: # INVESTIGATE MORE!
  622. target_rate = VIDEO_BITRATES[codec][25].get(resolution)
  623. if not target_rate:
  624. return False
  625. if media_info.get("video_height") < resolution:
  626. if resolution not in [240, 360]: # always get these two
  627. return False
  628. # if codec == "h264_baseline":
  629. # target_fps = 25
  630. # else:
  631. if media_info.get("video_duration") > CRF_ENCODING_NUM_SECONDS:
  632. enc_type = "crf"
  633. else:
  634. enc_type = "twopass"
  635. if enc_type == "twopass":
  636. passes = [1, 2]
  637. elif enc_type == "crf":
  638. passes = [2]
  639. interlaced = media_info.get("interlaced")
  640. cmds = []
  641. for pass_number in passes:
  642. cmds.append(
  643. get_base_ffmpeg_command(
  644. media_file,
  645. output_file=output_filename,
  646. has_audio=media_info.get("has_audio"),
  647. codec=codec,
  648. encoder=encoder,
  649. audio_encoder=AUDIO_ENCODERS[codec],
  650. target_fps=target_fps,
  651. interlaced=interlaced,
  652. target_height=resolution,
  653. target_rate=target_rate,
  654. target_rate_audio=AUDIO_BITRATES[codec],
  655. pass_file=pass_file,
  656. pass_number=pass_number,
  657. enc_type=enc_type,
  658. chunk=chunk,
  659. )
  660. )
  661. return cmds
  662. def clean_query(query):
  663. """This is used to clear text in order to comply with SearchQuery
  664. known exception cases
  665. :param query: str - the query text that we want to clean
  666. :return:
  667. """
  668. if not query:
  669. return ""
  670. chars = ["^", "{", "}", "&", "|", "<", ">", '"', ")", "(", "!", ":", ";", "'", "#"]
  671. for char in chars:
  672. query = query.replace(char, "")
  673. return query.lower()
  674. def get_alphanumeric_only(string):
  675. """Returns a query that contains only alphanumeric characters
  676. This include characters other than the English alphabet too
  677. """
  678. string = "".join([char for char in string if char.isalnum()])
  679. return string.lower()