import from scraps

This commit is contained in:
Aleksey 2024-01-13 12:50:03 +04:00
parent 0048227129
commit 506f1b7d97
Signed by: tea
GPG Key ID: D9C68D34A3CAE37A
4 changed files with 316 additions and 0 deletions

14
Pipfile Normal file
View File

@ -0,0 +1,14 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
opencv-python = "*"
ffmpeg-python = "*"
wand = "*"
[dev-packages]
[requires]
python_version = "3.12"

99
Pipfile.lock generated Normal file
View File

@ -0,0 +1,99 @@
{
"_meta": {
"hash": {
"sha256": "eb0d94e97384a638fdaf11adf4625c48a1c5899cba528d3dfca36f4ecc3870b7"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.12"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"ffmpeg-python": {
"hashes": [
"sha256:65225db34627c578ef0e11c8b1eb528bb35e024752f6f10b78c011f6f64c4127",
"sha256:ac441a0404e053f8b6a1113a77c0f452f1cfc62f6344a769475ffdc0f56c23c5"
],
"index": "pypi",
"version": "==0.2.0"
},
"future": {
"hashes": [
"sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
"version": "==0.18.3"
},
"numpy": {
"hashes": [
"sha256:02f98011ba4ab17f46f80f7f8f1c291ee7d855fcef0a5a98db80767a468c85cd",
"sha256:0b7e807d6888da0db6e7e75838444d62495e2b588b99e90dd80c3459594e857b",
"sha256:12c70ac274b32bc00c7f61b515126c9205323703abb99cd41836e8125ea0043e",
"sha256:1666f634cb3c80ccbd77ec97bc17337718f56d6658acf5d3b906ca03e90ce87f",
"sha256:18c3319a7d39b2c6a9e3bb75aab2304ab79a811ac0168a671a62e6346c29b03f",
"sha256:211ddd1e94817ed2d175b60b6374120244a4dd2287f4ece45d49228b4d529178",
"sha256:21a9484e75ad018974a2fdaa216524d64ed4212e418e0a551a2d83403b0531d3",
"sha256:39763aee6dfdd4878032361b30b2b12593fb445ddb66bbac802e2113eb8a6ac4",
"sha256:3c67423b3703f8fbd90f5adaa37f85b5794d3366948efe9a5190a5f3a83fc34e",
"sha256:46f47ee566d98849323f01b349d58f2557f02167ee301e5e28809a8c0e27a2d0",
"sha256:51c7f1b344f302067b02e0f5b5d2daa9ed4a721cf49f070280ac202738ea7f00",
"sha256:5f24750ef94d56ce6e33e4019a8a4d68cfdb1ef661a52cdaee628a56d2437419",
"sha256:697df43e2b6310ecc9d95f05d5ef20eacc09c7c4ecc9da3f235d39e71b7da1e4",
"sha256:6d45b3ec2faed4baca41c76617fcdcfa4f684ff7a151ce6fc78ad3b6e85af0a6",
"sha256:77810ef29e0fb1d289d225cabb9ee6cf4d11978a00bb99f7f8ec2132a84e0166",
"sha256:7ca4f24341df071877849eb2034948459ce3a07915c2734f1abb4018d9c49d7b",
"sha256:7f784e13e598e9594750b2ef6729bcd5a47f6cfe4a12cca13def35e06d8163e3",
"sha256:806dd64230dbbfaca8a27faa64e2f414bf1c6622ab78cc4264f7f5f028fee3bf",
"sha256:867e3644e208c8922a3be26fc6bbf112a035f50f0a86497f98f228c50c607bb2",
"sha256:8c66d6fec467e8c0f975818c1796d25c53521124b7cfb760114be0abad53a0a2",
"sha256:8ed07a90f5450d99dad60d3799f9c03c6566709bd53b497eb9ccad9a55867f36",
"sha256:9bc6d1a7f8cedd519c4b7b1156d98e051b726bf160715b769106661d567b3f03",
"sha256:9e1591f6ae98bcfac2a4bbf9221c0b92ab49762228f38287f6eeb5f3f55905ce",
"sha256:9e87562b91f68dd8b1c39149d0323b42e0082db7ddb8e934ab4c292094d575d6",
"sha256:a7081fd19a6d573e1a05e600c82a1c421011db7935ed0d5c483e9dd96b99cf13",
"sha256:a8474703bffc65ca15853d5fd4d06b18138ae90c17c8d12169968e998e448bb5",
"sha256:af36e0aa45e25c9f57bf684b1175e59ea05d9a7d3e8e87b7ae1a1da246f2767e",
"sha256:b1240f767f69d7c4c8a29adde2310b871153df9b26b5cb2b54a561ac85146485",
"sha256:b4d362e17bcb0011738c2d83e0a65ea8ce627057b2fdda37678f4374a382a137",
"sha256:b831295e5472954104ecb46cd98c08b98b49c69fdb7040483aff799a755a7374",
"sha256:b8c275f0ae90069496068c714387b4a0eba5d531aace269559ff2b43655edd58",
"sha256:bdd2b45bf079d9ad90377048e2747a0c82351989a2165821f0c96831b4a2a54b",
"sha256:cc0743f0302b94f397a4a65a660d4cd24267439eb16493fb3caad2e4389bccbb",
"sha256:da4b0c6c699a0ad73c810736303f7fbae483bcb012e38d7eb06a5e3b432c981b",
"sha256:f25e2811a9c932e43943a2615e65fc487a0b6b49218899e62e426e7f0a57eeda",
"sha256:f73497e8c38295aaa4741bdfa4fda1a5aedda5473074369eca10626835445511"
],
"markers": "python_version >= '3.10'",
"version": "==1.26.3"
},
"opencv-python": {
"hashes": [
"sha256:1a9f0e6267de3a1a1db0c54213d022c7c8b5b9ca4b580e80bdc58516c922c9e1",
"sha256:3f16f08e02b2a2da44259c7cc712e779eff1dd8b55fdb0323e8cab09548086c0",
"sha256:71dfb9555ccccdd77305fc3dcca5897fbf0cf28b297c51ee55e079c065d812a3",
"sha256:7b34a52e9da36dda8c151c6394aed602e4b17fa041df0b9f5b93ae10b0fcca2a",
"sha256:7e5f7aa4486651a6ebfa8ed4b594b65bd2d2f41beeb4241a3e4b1b85acbbbadb",
"sha256:dcf000c36dd1651118a2462257e3a9e76db789a78432e1f303c7bac54f63ef6c",
"sha256:e4088cab82b66a3b37ffc452976b14a3c599269c247895ae9ceb4066d8188a57"
],
"index": "pypi",
"version": "==4.9.0.80"
},
"wand": {
"hashes": [
"sha256:e5dda0ac2204a40c29ef5c4cb310770c95d3d05c37b1379e69c94ea79d7d19c0",
"sha256:f5013484eaf7a20eb22d1821aaefe60b50cc329722372b5f8565d46d4aaafcca"
],
"index": "pypi",
"version": "==0.6.13"
}
},
"develop": {}
}

197
cas.py Executable file
View File

@ -0,0 +1,197 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from argparse import ArgumentParser
from tempfile import TemporaryDirectory
import logging
import cv2
import asyncio
from pathlib import Path
from wand.image import Image
log = logging.getLogger('distortioner')
ap = ArgumentParser(
description = 'a script that makes memetic distorted videos'
)
class TicketedDict(dict):
def __init__(self, *args, **kwargs):
self._ni = 0
self._log = logging.getLogger('TicketedDict')
self.event = asyncio.Event()
super().__init__(*args, **kwargs)
def has_next(self):
self._log.debug("has_next is %s for %i", self._ni in self, self._ni)
return self._ni in self
async def wait(self):
self._log.debug("requested wait")
while not self.has_next():
await self.event.wait()
self._log.debug("wait broke")
self.event.clear()
async def pop(self):
self._log.debug("requested pop")
await self.wait()
ret = super().pop(self._ni)
self._ni += 1
return ret
def notify(self, *args, **kwargs):
self._log.debug("requested notify")
return self.event.set(*args, **kwargs)
ap.add_argument('input')
ap.add_argument('output')
ap.add_argument(
'--distort-percentage','--distort-pct', '-d',
default = 60.0,
type = float,
help = 'Percentage of image distortion.'
)
ap.add_argument(
'--distort-percentage-end','--distort-pct-end', '-D',
default = None,
type = float,
help = 'If specified, distortion percentage will gradually change towards specified percentage.'
)
ap.add_argument(
'--distort-end','--distort', '-E',
default = None,
type = float,
help = 'If specified, distortion change will happen only up to specific video progress.'
)
ap.add_argument(
'--vibrato-frequency','--vibrato-freq', '-f',
default = 5.0,
type = float,
help = 'Modulation frequency in Hertz. Range is 0.1 - 20000.0. Default value is 5.0 Hz.'
)
ap.add_argument(
'--vibrato-modulation-depth','--vibrato-depth', '-m',
default = 0.5,
type = float,
help = 'Depth of modulation as a percentage. Range is 0.0 - 1.0. Default value is 0.5.'
)
ap.add_argument(
'--debug',
action = 'store_true',
default = False,
help = 'Print debugging messages.'
)
def process_image(source, destination, distort):
log.debug("distorting: src:'%s', dst:'%s' ", source, destination)
with Image(filename=source) as original:
dst_width = int(original.width*(distort / 100.))
dst_height = int(original.height*(distort / 100.))
log.debug("dst:'%s' size:%ix%i", destination, dst_width, dst_height)
with original.clone() as distorted, \
open(destination, mode='wb') as out:
distorted.liquid_rescale(dst_width, dst_height)
distorted.resize(original.width, original.height)
distorted.save(out)
async def process_frames(coro, queue_in, out_pile):
while True:
frame_data = await queue_in.get()
nr = frame_data.pop('nr')
log.debug("processing frame '%s'", frame_data)
await asyncio.to_thread(coro, **frame_data)
queue_in.task_done()
out_pile[nr]=frame_data['destination']
log.debug("put item to output pile, notifying")
out_pile.notify()
log.debug("notified")
async def read_frames(capture, frames_distorted, frames_original, queue, tasks, distort_start, distort_end=None, distort_end_frame=None):
frames_read = 0
if distort_end_frame is None:
distort_end_frame = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
while True:
read_ok, frame = capture.read()
if not read_ok:
break
log.debug("reading frame %i", frames_read)
if distort_end is not None:
distort = distort_start \
+ (distort_end - distort_start) \
* (min(frames_read, distort_end_frame) / distort_end_frame )
frame_filename = f'frame_{str(frames_read).zfill(32)}.png'
frame_original = str(Path(frames_original)/frame_filename)
frame_distorted = str(Path(frames_distorted)/frame_filename)
cv2.imwrite(frame_original, frame)
log.debug("saving frame %i: filename: %s", frames_read, frame_filename)
await queue.put({
'source': frame_original,
'destination': frame_distorted,
'distort': distort,
'nr': frames_read
})
frames_read += 1
log.info("waiting for queue to empty")
await queue.join()
log.info("quitting")
for worker in tasks:
worker.cancel()
async def write_frames(output, pile):
while True:
log.debug("getting next item ...")
frame_distorted = await pile.pop()
log.info("writing frame '%s'", frame_distorted)
newframe = cv2.imread(frame_distorted)
output.write(newframe)
log.debug("finished frame '%s'", frame_distorted)
async def distort_video(capture, output, distort_start, distort_end=None, distort_end_frame=None):
distort = distort_start
video_width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
video_height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
output_pile = TicketedDict()
with \
TemporaryDirectory() as frames_distorted, \
TemporaryDirectory() as frames_original:
log.debug(frames_distorted)
log.debug(frames_original)
log.debug("creating queues")
capture_queue = asyncio.Queue(20)
output_queue = asyncio.PriorityQueue()
workers = [ asyncio.create_task(process_frames(process_image, capture_queue, output_pile)) for i in range(10) ]
workers += [ asyncio.create_task(write_frames(output, output_pile)) ]
generator = asyncio.create_task(read_frames(
capture, frames_distorted, frames_original, capture_queue, workers, distort_start, distort_end, distort_end_frame
))
await asyncio.gather(generator, *workers, return_exceptions=True)
log.debug('done with distorting video frames')
def main():
args = ap.parse_args()
if args.debug:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)
log.debug(args)
if args.distort_percentage <= 0.0:
raise ValueError("--distort_percentage must be positive number")
capture = cv2.VideoCapture(args.input)
fps = capture.get(cv2.CAP_PROP_FPS)
video_width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
video_height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
frames = args.distort_end or int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
log.info("video: name:'%s', fps:%i, frames:%i, size:%ix%i", args.input, fps, frames, video_width, video_height)
output = cv2.VideoWriter(args.output, cv2.VideoWriter_fourcc(*'mp4v'), fps, (video_width, video_height))
asyncio.run(distort_video(capture, output, args.distort_percentage, args.distort_percentage_end, frames-1))
capture.release()
output.release()
if __name__ == '__main__':
main()

6
requirements.txt Normal file
View File

@ -0,0 +1,6 @@
-i https://pypi.org/simple
ffmpeg-python==0.2.0
future==0.18.3 ; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'
numpy==1.26.3 ; python_version >= '3.10'
opencv-python==4.9.0.80
wand==0.6.13