diff --git a/scratchpad/README b/scratchpad/README deleted file mode 100644 index 83d571e01..000000000 --- a/scratchpad/README +++ /dev/null @@ -1,4 +0,0 @@ -This directory is for the PyAV developers to dump partial or exprimental tests. -The contents of this directory are not guaranteed to work, or make sense in any way. - -Have fun! diff --git a/scratchpad/__init__.py b/scratchpad/__init__.py deleted file mode 100644 index ab4bc5141..000000000 --- a/scratchpad/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -import logging - -logging.basicConfig(level=logging.WARNING) diff --git a/scratchpad/audio.py b/scratchpad/audio.py deleted file mode 100644 index addae0915..000000000 --- a/scratchpad/audio.py +++ /dev/null @@ -1,100 +0,0 @@ -import array -import argparse -import sys -import pprint -import subprocess - -from PIL import Image -import av - - -def print_data(frame): - for i, plane in enumerate(frame.planes or ()): - data = bytes(plane) - print('\tPLANE %d, %d bytes' % (i, len(data))) - data = data.encode('hex') - for i in range(0, len(data), 128): - print('\t\t\t%s' % data[i:i + 128]) - - -arg_parser = argparse.ArgumentParser() -arg_parser.add_argument('path') -arg_parser.add_argument('-p', '--play', action='store_true') -arg_parser.add_argument('-d', '--data', action='store_true') -arg_parser.add_argument('-f', '--format') -arg_parser.add_argument('-l', '--layout') -arg_parser.add_argument('-r', '--rate', type=int) -arg_parser.add_argument('-s', '--size', type=int, default=1024) -arg_parser.add_argument('-c', '--count', type=int, default=5) -args = arg_parser.parse_args() - -ffplay = None - -container = av.open(args.path) -stream = next(s for s in container.streams if s.type == 'audio') - -fifo = av.AudioFifo() if args.size else None -resampler = av.AudioResampler( - format=av.AudioFormat(args.format or stream.format.name).packed if args.format else None, - layout=int(args.layout) if args.layout and args.layout.isdigit() else args.layout, - rate=args.rate, -) if (args.format or args.layout or args.rate) else None - -read_count = 0 -fifo_count = 0 -sample_count = 0 - -for i, packet in enumerate(container.demux(stream)): - - for frame in packet.decode(): - - read_count += 1 - print('>>>> %04d' % read_count, frame) - if args.data: - print_data(frame) - - frames = [frame] - - if resampler: - for i, frame in enumerate(frames): - frame = resampler.resample(frame) - print('RESAMPLED', frame) - if args.data: - print_data(frame) - frames[i] = frame - - if fifo: - - to_process = frames - frames = [] - - for frame in to_process: - fifo.write(frame) - while frame: - frame = fifo.read(args.size) - if frame: - fifo_count += 1 - print('|||| %04d' % fifo_count, frame) - if args.data: - print_data(frame) - frames.append(frame) - - if frames and args.play: - if not ffplay: - cmd = ['ffplay', - '-f', frames[0].format.packed.container_name, - '-ar', str(args.rate or stream.rate), - '-ac', str(len(resampler.layout.channels if resampler else stream.layout.channels)), - '-vn', '-', - ] - print('PLAY', ' '.join(cmd)) - ffplay = subprocess.Popen(cmd, stdin=subprocess.PIPE) - try: - for frame in frames: - ffplay.stdin.write(bytes(frame.planes[0])) - except IOError as e: - print(e) - exit() - - if args.count and read_count >= args.count: - exit() diff --git a/scratchpad/audio_player.py b/scratchpad/audio_player.py deleted file mode 100644 index 8322a3206..000000000 --- a/scratchpad/audio_player.py +++ /dev/null @@ -1,81 +0,0 @@ -import array -import argparse -import sys -import pprint -import subprocess -import time - -from qtproxy import Q - -import av - - -parser = argparse.ArgumentParser() -parser.add_argument('path') -args = parser.parse_args() - -container = av.open(args.path) -stream = next(s for s in container.streams if s.type == 'audio') - -fifo = av.AudioFifo() -resampler = av.AudioResampler( - format=av.AudioFormat('s16').packed, - layout='stereo', - rate=48000, -) - - - -qformat = Q.AudioFormat() -qformat.setByteOrder(Q.AudioFormat.LittleEndian) -qformat.setChannelCount(2) -qformat.setCodec('audio/pcm') -qformat.setSampleRate(48000) -qformat.setSampleSize(16) -qformat.setSampleType(Q.AudioFormat.SignedInt) - -output = Q.AudioOutput(qformat) -output.setBufferSize(2 * 2 * 48000) - -device = output.start() - -print(qformat, output, device) - -def decode_iter(): - try: - for pi, packet in enumerate(container.demux(stream)): - for fi, frame in enumerate(packet.decode()): - yield pi, fi, frame - except: - return - -for pi, fi, frame in decode_iter(): - - frame = resampler.resample(frame) - print(pi, fi, frame, output.state()) - - bytes_buffered = output.bufferSize() - output.bytesFree() - us_processed = output.processedUSecs() - us_buffered = 1000000 * bytes_buffered / (2 * 16 / 8) / 48000 - print('pts: %.3f, played: %.3f, buffered: %.3f' % (frame.time or 0, us_processed / 1000000.0, us_buffered / 1000000.0)) - - - data = bytes(frame.planes[0]) - while data: - written = device.write(data) - if written: - # print 'wrote', written - data = data[written:] - else: - # print 'did not accept data; sleeping' - time.sleep(0.033) - - if False and pi % 100 == 0: - output.reset() - print(output.state(), output.error()) - device = output.start() - - # time.sleep(0.05) - -while output.state() == Q.Audio.ActiveState: - time.sleep(0.1) diff --git a/scratchpad/average.py b/scratchpad/average.py deleted file mode 100644 index f72297f08..000000000 --- a/scratchpad/average.py +++ /dev/null @@ -1,59 +0,0 @@ -import argparse -import os -import sys -import pprint -import itertools - -import cv2 - -from av import open - - -parser = argparse.ArgumentParser() -parser.add_argument('-f', '--format') -parser.add_argument('-n', '--frames', type=int, default=0) -parser.add_argument('path', nargs='+') -args = parser.parse_args() - -max_size = 24 * 60 # One minute's worth. - - -def frame_iter(video): - count = 0 - streams = [s for s in video.streams if s.type == b'video'] - streams = [streams[0]] - for packet in video.demux(streams): - for frame in packet.decode(): - yield frame - count += 1 - if args.frames and count > args.frames: - return - - -for src_path in args.path: - - print('reading', src_path) - - basename = os.path.splitext(os.path.basename(src_path))[0] - dir_name = os.path.join('sandbox', basename) - if not os.path.exists(dir_name): - os.makedirs(dir_name) - - video = open(src_path, format=args.format) - frames = frame_iter(video) - - sum_ = None - - for fi, frame in enumerate(frame_iter(video)): - - if sum_ is None: - sum_ = frame.to_ndarray().astype(float) - else: - sum_ += frame.to_ndarray().astype(float) - - sum_ /= (fi + 1) - - dst_path = os.path.join('sandbox', os.path.basename(src_path) + '-avg.jpeg') - print('writing', (fi + 1), 'frames to', dst_path) - - cv2.imwrite(dst_path, sum_) diff --git a/scratchpad/cctx_decode.py b/scratchpad/cctx_decode.py deleted file mode 100644 index bdb6724f4..000000000 --- a/scratchpad/cctx_decode.py +++ /dev/null @@ -1,32 +0,0 @@ -import logging - -logging.basicConfig() - - -import av -from av.codec import CodecContext, CodecParser -from av.video import VideoFrame -from av.packet import Packet - - -cc = CodecContext.create('mpeg4', 'r') -print(cc) - - -fh = open('test.mp4', 'r') - -frame_count = 0 - -while True: - - chunk = fh.read(819200) - for packet in cc.parse(chunk or None, allow_stream=True): - print(packet) - for frame in cc.decode(packet) or (): - print(frame) - img = frame.to_image() - img.save('sandbox/test.%04d.jpg' % frame_count) - frame_count += 1 - - if not chunk: - break # EOF! diff --git a/scratchpad/cctx_encode.py b/scratchpad/cctx_encode.py deleted file mode 100644 index 165203c1c..000000000 --- a/scratchpad/cctx_encode.py +++ /dev/null @@ -1,47 +0,0 @@ -import logging - -from PIL import Image, ImageDraw, ImageFont - -from av.codec import CodecContext -from av.video import VideoFrame -from tests.common import fate_suite - - -logging.basicConfig() - - -cc = CodecContext.create('flv', 'w') -print(cc) - -base_img = Image.open(fate_suite('png1/lena-rgb24.png')) -font = ImageFont.truetype("/System/Library/Fonts/Menlo.ttc", 15) - - -fh = open('test.flv', 'wb') - -for i in range(30): - - print(i) - img = base_img.copy() - draw = ImageDraw.Draw(img) - draw.text((10, 10), "FRAME %02d" % i, font=font) - - frame = VideoFrame.from_image(img) - frame = frame.reformat(format='yuv420p') - print(' ', frame) - - packet = cc.encode(frame) - print(' ', packet) - - fh.write(bytes(packet)) - -print('Flushing...') - -while True: - packet = cc.encode() - if not packet: - break - print(' ', packet) - fh.write(bytes(packet)) - -print('Done!') diff --git a/scratchpad/container-gc.py b/scratchpad/container-gc.py deleted file mode 100644 index a5bd2130d..000000000 --- a/scratchpad/container-gc.py +++ /dev/null @@ -1,44 +0,0 @@ -import resource -import gc - - -import av -import av.datasets - -path = av.datasets.curated('pexels/time-lapse-video-of-night-sky-857195.mp4') - - -def format_bytes(n): - order = 0 - while n > 1024: - order += 1 - n //= 1024 - return '%d%sB' % (n, ('', 'k', 'M', 'G', 'T', 'P')[order]) - -after = resource.getrusage(resource.RUSAGE_SELF) - -count = 0 - -streams = [] - -while True: - - container = av.open(path) - # streams.append(container.streams.video[0]) - - del container - gc.collect() - - count += 1 - if not count % 100: - pass - # streams.clear() - # gc.collect() - - before = after - after = resource.getrusage(resource.RUSAGE_SELF) - print('{:6d} {} ({})'.format( - count, - format_bytes(after.ru_maxrss), - format_bytes(after.ru_maxrss - before.ru_maxrss), - )) diff --git a/scratchpad/decode.py b/scratchpad/decode.py deleted file mode 100644 index edfb413b7..000000000 --- a/scratchpad/decode.py +++ /dev/null @@ -1,164 +0,0 @@ -import array -import argparse -import logging -import sys -import pprint -import subprocess - -from PIL import Image - -from av import open, time_base - - -logging.basicConfig(level=logging.DEBUG) - - -def format_time(time, time_base): - if time is None: - return 'None' - return '%.3fs (%s or %s/%s)' % (time_base * time, time_base * time, time_base.numerator * time, time_base.denominator) - - -arg_parser = argparse.ArgumentParser() -arg_parser.add_argument('path') -arg_parser.add_argument('-f', '--format') -arg_parser.add_argument('-a', '--audio', action='store_true') -arg_parser.add_argument('-v', '--video', action='store_true') -arg_parser.add_argument('-s', '--subs', action='store_true') -arg_parser.add_argument('-d', '--data', action='store_true') -arg_parser.add_argument('--dump-packets', action='store_true') -arg_parser.add_argument('--dump-planes', action='store_true') -arg_parser.add_argument('-p', '--play', action='store_true') -arg_parser.add_argument('-t', '--thread-type') -arg_parser.add_argument('-o', '--option', action='append', default=[]) -arg_parser.add_argument('-c', '--count', type=int, default=5) -args = arg_parser.parse_args() - - -proc = None - -options = dict(x.split('=') for x in args.option) -container = open(args.path, format=args.format, options=options) - -print('container:', container) -print('\tformat:', container.format) -print('\tduration:', float(container.duration) / time_base) -print('\tmetadata:') -for k, v in sorted(container.metadata.items()): - print('\t\t%s: %r' % (k, v)) -print() - -print(len(container.streams), 'stream(s):') -for i, stream in enumerate(container.streams): - - if args.thread_type: - stream.codec_context.thread_type = args.thread_type - - print('\t%r' % stream) - print('\t\ttime_base: %r' % stream.time_base) - print('\t\trate: %r' % stream.rate) - print('\t\tstart_time: %r' % stream.start_time) - print('\t\tduration: %s' % format_time(stream.duration, stream.time_base)) - print('\t\tbit_rate: %r' % stream.bit_rate) - print('\t\tbit_rate_tolerance: %r' % stream.bit_rate_tolerance) - - codec_context = stream.codec_context - if codec_context: - print('\t\tcodec_context:', codec_context) - print('\t\t\ttime_base:', codec_context.time_base) - - if stream.type == b'audio': - print('\t\taudio:') - print('\t\t\tformat:', stream.format) - print('\t\t\tchannels: %s' % stream.channels) - - elif stream.type == 'video': - print('\t\tvideo:') - print('\t\t\tformat:', stream.format) - print('\t\t\taverage_rate: %r' % stream.average_rate) - - print('\t\tmetadata:') - for k, v in sorted(stream.metadata.items()): - print('\t\t\t%s: %r' % (k, v)) - - print() - - -streams = [s for s in container.streams if - (s.type == 'audio' and args.audio) or - (s.type == 'video' and args.video) or - (s.type == 'subtitle' and args.subs) or - (s.type == 'data' and args.data) -] - - -frame_count = 0 - -for i, packet in enumerate(container.demux(streams)): - - print('%02d %r' % (i, packet)) - print('\ttime_base: %s' % packet.time_base) - print('\tduration: %s' % format_time(packet.duration, packet.stream.time_base)) - print('\tpts: %s' % format_time(packet.pts, packet.stream.time_base)) - print('\tdts: %s' % format_time(packet.dts, packet.stream.time_base)) - print('\tkey: %s' % packet.is_keyframe) - - if args.dump_packets: - print(bytes(packet)) - - for frame in packet.decode(): - - frame_count += 1 - - print('\tdecoded:', frame) - print('\t\ttime_base: %s' % frame.time_base) - print('\t\tpts:', format_time(frame.pts, packet.stream.time_base)) - - if packet.stream.type == 'video': - pass - - elif packet.stream.type == 'audio': - print('\t\tsamples:', frame.samples) - print('\t\tformat:', frame.format.name) - print('\t\tlayout:', frame.layout.name) - - elif packet.stream.type == 'subtitle': - - sub = frame - - print('\t\tformat:', sub.format) - print('\t\tstart_display_time:', format_time(sub.start_display_time, packet.stream.time_base)) - print('\t\tend_display_time:', format_time(sub.end_display_time, packet.stream.time_base)) - print('\t\trects: %d' % len(sub.rects)) - for rect in sub.rects: - print('\t\t\t%r' % rect) - if rect.type == 'ass': - print('\t\t\t\tass: %r' % rect.ass) - - if args.play and packet.stream.type == 'audio': - if not proc: - cmd = ['ffplay', - '-f', 's16le', - '-ar', str(packet.stream.time_base), - '-vn', '-', - ] - proc = subprocess.Popen(cmd, stdin=subprocess.PIPE) - try: - proc.stdin.write(bytes(frame.planes[0])) - except IOError as e: - print(e) - exit() - - if args.dump_planes: - print('\t\tplanes') - for i, plane in enumerate(frame.planes or ()): - data = bytes(plane) - print('\t\t\tPLANE %d, %d bytes' % (i, len(data))) - data = data.encode('hex') - for i in range(0, len(data), 128): - print('\t\t\t%s' % data[i:i + 128]) - - if args.count and frame_count >= args.count: - exit() - - print() diff --git a/scratchpad/dump_format.py b/scratchpad/dump_format.py deleted file mode 100644 index 80682fb47..000000000 --- a/scratchpad/dump_format.py +++ /dev/null @@ -1,10 +0,0 @@ -import sys -import logging - -logging.basicConfig(level=logging.DEBUG) -logging.getLogger('libav').setLevel(logging.DEBUG) - -import av - -fh = av.open(sys.argv[1]) -print(fh.dumps_format()) diff --git a/scratchpad/encode.py b/scratchpad/encode.py deleted file mode 100644 index 099ac4a14..000000000 --- a/scratchpad/encode.py +++ /dev/null @@ -1,91 +0,0 @@ -import argparse -import logging -import os -import sys - -import av -from tests.common import asset, sandboxed - - -arg_parser = argparse.ArgumentParser() -arg_parser.add_argument('-v', '--verbose', action='store_true') -arg_parser.add_argument('input', nargs=1) -args = arg_parser.parse_args() - -input_file = av.open(args.input[0]) -input_video_stream = None # next((s for s in input_file.streams if s.type == 'video'), None) -input_audio_stream = next((s for s in input_file.streams if s.type == 'audio'), None) - -# open output file -output_file_path = sandboxed('encoded-' + os.path.basename(args.input[0])) -output_file = av.open(output_file_path, 'w') -output_video_stream = output_file.add_stream("mpeg4", 24) if input_video_stream else None -output_audio_stream = output_file.add_stream("mp3") if input_audio_stream else None - - -frame_count = 0 - - -for packet in input_file.demux([s for s in (input_video_stream, input_audio_stream) if s]): - - - if args.verbose: - print('in ', packet) - - for frame in packet.decode(): - - if args.verbose: - print('\t%s' % frame) - - if packet.stream.type == b'video': - if frame_count % 10 == 0: - if frame_count: - print() - print(('%03d:' % frame_count), end=' ') - sys.stdout.write('.') - sys.stdout.flush() - - frame_count += 1 - - # Signal to generate it's own timestamps. - frame.pts = None - - stream = output_audio_stream if packet.stream.type == b'audio' else output_video_stream - output_packets = [output_audio_stream.encode(frame)] - while output_packets[-1]: - output_packets.append(output_audio_stream.encode(None)) - - for p in output_packets: - if p: - if args.verbose: - print('OUT', p) - output_file.mux(p) - - if frame_count >= 100: - break - -print('-' * 78) - -# Finally we need to flush out the frames that are buffered in the encoder. -# To do that we simply call encode with no args until we get a None returned -if output_audio_stream: - while True: - output_packet = output_audio_stream.encode(None) - if output_packet: - if args.verbose: - print('<<<', output_packet) - output_file.mux(output_packet) - else: - break - -if output_video_stream: - while True: - output_packet = output_video_stream.encode(None) - if output_packet: - if args.verbose: - print('<<<', output_packet) - output_file.mux(output_packet) - else: - break - -output_file.close() diff --git a/scratchpad/encode_frames.py b/scratchpad/encode_frames.py deleted file mode 100644 index 642a1c2c6..000000000 --- a/scratchpad/encode_frames.py +++ /dev/null @@ -1,40 +0,0 @@ -import argparse -import os -import sys - -import av -import cv2 - - -arg_parser = argparse.ArgumentParser() -arg_parser.add_argument('-r', '--rate', default='23.976') -arg_parser.add_argument('-f', '--format', default='yuv420p') -arg_parser.add_argument('-w', '--width', type=int) -arg_parser.add_argument('--height', type=int) -arg_parser.add_argument('-b', '--bitrate', type=int, default=8000000) -arg_parser.add_argument('-c', '--codec', default='mpeg4') -arg_parser.add_argument('inputs', nargs='+') -arg_parser.add_argument('output', nargs=1) -args = arg_parser.parse_args() - - -output = av.open(args.output[0], 'w') -stream = output.add_stream(args.codec, args.rate) -stream.bit_rate = args.bitrate -stream.pix_fmt = args.format - -for i, path in enumerate(args.inputs): - - print(os.path.basename(path)) - - img = cv2.imread(path) - - if not i: - stream.height = args.height or (args.width * img.shape[0] / img.shape[1]) or img.shape[0] - stream.width = args.width or img.shape[1] - - frame = av.VideoFrame.from_ndarray(img, format='bgr24') - packet = stream.encode(frame) - output.mux(packet) - -output.close() diff --git a/scratchpad/filter_audio.py b/scratchpad/filter_audio.py deleted file mode 100644 index 092aba3ae..000000000 --- a/scratchpad/filter_audio.py +++ /dev/null @@ -1,122 +0,0 @@ -""" -Simple audio filtering example ported from C code: - https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/filter_audio.c -""" -from fractions import Fraction -import hashlib -import sys - -import numpy as np - -import av -import av.audio.frame as af -import av.filter - - -FRAME_SIZE = 1024 - -INPUT_SAMPLE_RATE = 48000 -INPUT_FORMAT = 'fltp' -INPUT_CHANNEL_LAYOUT = '5.0(side)' # -> AV_CH_LAYOUT_5POINT0 - -OUTPUT_SAMPLE_RATE = 44100 -OUTPUT_FORMAT = 's16' # notice, packed audio format, expect only one plane in output -OUTPUT_CHANNEL_LAYOUT = 'stereo' # -> AV_CH_LAYOUT_STEREO - -VOLUME_VAL = 0.90 - - -def init_filter_graph(): - graph = av.filter.Graph() - - output_format = 'sample_fmts={}:sample_rates={}:channel_layouts={}'.format( - OUTPUT_FORMAT, - OUTPUT_SAMPLE_RATE, - OUTPUT_CHANNEL_LAYOUT - ) - print('Output format: {}'.format(output_format)) - - # initialize filters - filter_chain = [ - graph.add_abuffer(format=INPUT_FORMAT, - sample_rate=INPUT_SAMPLE_RATE, - layout=INPUT_CHANNEL_LAYOUT, - time_base=Fraction(1, INPUT_SAMPLE_RATE)), - # initialize filter with keyword parameters - graph.add('volume', volume=str(VOLUME_VAL)), - # or compound string configuration - graph.add('aformat', output_format), - graph.add('abuffersink') - ] - - # link up the filters into a chain - print('Filter graph:') - for c, n in zip(filter_chain, filter_chain[1:]): - print('\t{} -> {}'.format(c, n)) - c.link_to(n) - - # initialize the filter graph - graph.configure() - - return graph - - -def get_input(frame_num): - """ - Manually construct and update AudioFrame. - Consider using AudioFrame.from_ndarry for most real life numpy->AudioFrame conversions. - - :param frame_num: - :return: - """ - frame = av.AudioFrame(format=INPUT_FORMAT, layout=INPUT_CHANNEL_LAYOUT, samples=FRAME_SIZE) - frame.sample_rate = INPUT_SAMPLE_RATE - frame.pts = frame_num * FRAME_SIZE - - for i in range(len(frame.layout.channels)): - data = np.zeros(FRAME_SIZE, dtype=af.format_dtypes[INPUT_FORMAT]) - for j in range(FRAME_SIZE): - data[j] = np.sin(2 * np.pi * (frame_num + j) * (i + 1) / float(FRAME_SIZE)) - frame.planes[i].update(data) - - return frame - - -def process_output(frame): - data = frame.to_ndarray() - for i in range(data.shape[0]): - m = hashlib.md5(data[i, :].tobytes()) - print('Plane: {:0d} checksum: {}'.format(i, m.hexdigest())) - - -def main(duration): - frames_count = int(duration * INPUT_SAMPLE_RATE / FRAME_SIZE) - - graph = init_filter_graph() - - for f in range(frames_count): - frame = get_input(f) - - # submit the frame for processing - graph.push(frame) - - # pull frames from graph until graph has done processing or is waiting for a new input - while True: - try: - out_frame = graph.pull() - process_output(out_frame) - except (BlockingIOError, av.EOFError): - break - - # process any remaining buffered frames - while True: - try: - out_frame = graph.pull() - process_output(out_frame) - except (BlockingIOError, av.EOFError): - break - - -if __name__ == '__main__': - duration = 1.0 if len(sys.argv) < 2 else float(sys.argv[1]) - main(duration) diff --git a/scratchpad/frame_seek_example.py b/scratchpad/frame_seek_example.py deleted file mode 100644 index 25b2d27d0..000000000 --- a/scratchpad/frame_seek_example.py +++ /dev/null @@ -1,418 +0,0 @@ -""" -Note this example only really works accurately on constant frame rate media. -""" -from PyQt4 import QtGui -from PyQt4 import QtCore -from PyQt4.QtCore import Qt - -import sys -import av - - -AV_TIME_BASE = 1000000 - -def pts_to_frame(pts, time_base, frame_rate, start_time): - return int(pts * time_base * frame_rate) - int(start_time * time_base * frame_rate) - -def get_frame_rate(stream): - - if stream.average_rate.denominator and stream.average_rate.numerator: - return float(stream.average_rate) - if stream.time_base.denominator and stream.time_base.numerator: - return 1.0 / float(stream.time_base) - else: - raise ValueError("Unable to determine FPS") - -def get_frame_count(f, stream): - - if stream.frames: - return stream.frames - elif stream.duration: - return pts_to_frame(stream.duration, float(stream.time_base), get_frame_rate(stream), 0) - elif f.duration: - return pts_to_frame(f.duration, 1 / float(AV_TIME_BASE), get_frame_rate(stream), 0) - - else: - raise ValueError("Unable to determine number for frames") - -class FrameGrabber(QtCore.QObject): - - frame_ready = QtCore.pyqtSignal(object, object) - update_frame_range = QtCore.pyqtSignal(object) - - def __init__(self, parent=None): - super(FrameGrabber, self).__init__(parent) - self.file = None - self.stream = None - self.frame = None - self.active_frame = None - self.start_time = 0 - self.pts_seen = False - self.nb_frames = None - - self.rate = None - self.time_base = None - - def next_frame(self): - - frame_index = None - - rate = self.rate - time_base = self.time_base - - self.pts_seen = False - - for packet in self.file.demux(self.stream): - #print " pkt", packet.pts, packet.dts, packet - if packet.pts: - self.pts_seen = True - - for frame in packet.decode(): - - if frame_index is None: - - if self.pts_seen: - pts = frame.pts - else: - pts = frame.dts - - if not pts is None: - frame_index = pts_to_frame(pts, time_base, rate, self.start_time) - - elif not frame_index is None: - frame_index += 1 - - - yield frame_index, frame - - - @QtCore.pyqtSlot(object) - def request_frame(self, target_frame): - - frame = self.get_frame(target_frame) - if not frame: - return - - rgba = frame.reformat(frame.width, frame.height, "rgb24", 'itu709') - #print rgba.to_image().save("test.png") - # could use the buffer interface here instead, some versions of PyQt don't support it for some reason - # need to track down which version they added support for it - self.frame = bytearray(rgba.planes[0]) - bytesPerPixel = 3 - img = QtGui.QImage(self.frame, rgba.width, rgba.height, rgba.width * bytesPerPixel, QtGui.QImage.Format_RGB888) - - #img = QtGui.QImage(rgba.planes[0], rgba.width, rgba.height, QtGui.QImage.Format_RGB888) - - #pixmap = QtGui.QPixmap.fromImage(img) - self.frame_ready.emit(img, target_frame) - - def get_frame(self, target_frame): - - if target_frame != self.active_frame: - return - print('seeking to', target_frame) - - seek_frame = target_frame - - rate = self.rate - time_base = self.time_base - - frame = None - reseek = 250 - - original_target_frame_pts = None - - while reseek >= 0: - - # convert seek_frame to pts - target_sec = seek_frame * 1 / rate - target_pts = int(target_sec / time_base) + self.start_time - - if original_target_frame_pts is None: - original_target_frame_pts = target_pts - - self.stream.seek(int(target_pts)) - - frame_index = None - - frame_cache = [] - - for i, (frame_index, frame) in enumerate(self.next_frame()): - - # optimization if the time slider has changed, the requested frame no longer valid - if target_frame != self.active_frame: - return - - print(" ", i, "at frame", frame_index, "at ts:", frame.pts, frame.dts, "target:", target_pts, 'orig', original_target_frame_pts) - - if frame_index is None: - pass - - elif frame_index >= target_frame: - break - - frame_cache.append(frame) - - # Check if we over seeked, if we over seekd we need to seek to a earlier time - # but still looking for the target frame - if frame_index != target_frame: - - if frame_index is None: - over_seek = '?' - else: - over_seek = frame_index - target_frame - if frame_index > target_frame: - - print(over_seek, frame_cache) - if over_seek <= len(frame_cache): - print("over seeked by %i, using cache" % over_seek) - frame = frame_cache[-over_seek] - break - - - seek_frame -= 1 - reseek -= 1 - print("over seeked by %s, backtracking.. seeking: %i target: %i retry: %i" % (str(over_seek), seek_frame, target_frame, reseek)) - - else: - break - - if reseek < 0: - raise ValueError("seeking failed %i" % frame_index) - - # frame at this point should be the correct frame - - if frame: - - return frame - - else: - raise ValueError("seeking failed %i" % target_frame) - - def get_frame_count(self): - - frame_count = None - - if self.stream.frames: - frame_count = self.stream.frames - elif self.stream.duration: - frame_count = pts_to_frame(self.stream.duration, float(self.stream.time_base), get_frame_rate(self.stream), 0) - elif self.file.duration: - frame_count = pts_to_frame(self.file.duration, 1 / float(AV_TIME_BASE), get_frame_rate(self.stream), 0) - else: - raise ValueError("Unable to determine number for frames") - - seek_frame = frame_count - - retry = 100 - - while retry: - target_sec = seek_frame * 1 / self.rate - target_pts = int(target_sec / self.time_base) + self.start_time - - self.stream.seek(int(target_pts)) - - frame_index = None - - for frame_index, frame in self.next_frame(): - print(frame_index, frame) - continue - - if not frame_index is None: - break - else: - seek_frame -= 1 - retry -= 1 - - - print("frame count seeked", frame_index, "container frame count", frame_count) - - return frame_index or frame_count - - @QtCore.pyqtSlot(object) - def set_file(self, path): - self.file = av.open(path) - self.stream = next(s for s in self.file.streams if s.type == b'video') - self.rate = get_frame_rate(self.stream) - self.time_base = float(self.stream.time_base) - - - index, first_frame = next(self.next_frame()) - self.stream.seek(self.stream.start_time) - - # find the pts of the first frame - index, first_frame = next(self.next_frame()) - - if self.pts_seen: - pts = first_frame.pts - else: - pts = first_frame.dts - - self.start_time = pts or first_frame.dts - - print("First pts", pts, self.stream.start_time, first_frame) - - #self.nb_frames = get_frame_count(self.file, self.stream) - self.nb_frames = self.get_frame_count() - - self.update_frame_range.emit(self.nb_frames) - - - - - -class DisplayWidget(QtGui.QLabel): - def __init__(self, parent=None): - super(DisplayWidget, self).__init__(parent) - #self.setScaledContents(True) - self.setMinimumSize(1920 / 10, 1080 / 10) - - size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) - size_policy.setHeightForWidth(True) - - self.setSizePolicy(size_policy) - - self.setAlignment(Qt.AlignHCenter | Qt.AlignBottom) - - self.pixmap = None - self.setMargin(10) - - def heightForWidth(self, width): - return width * 9 / 16.0 - - @QtCore.pyqtSlot(object, object) - def setPixmap(self, img, index): - #if index == self.current_index: - self.pixmap = QtGui.QPixmap.fromImage(img) - - #super(DisplayWidget, self).setPixmap(self.pixmap) - super(DisplayWidget, self).setPixmap(self.pixmap.scaled(self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) - - def sizeHint(self): - width = self.width() - return QtCore.QSize(width, self.heightForWidth(width)) - - def resizeEvent(self, event): - if self.pixmap: - super(DisplayWidget, self).setPixmap(self.pixmap.scaled(self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) - - def sizeHint(self): - return QtCore.QSize(1920 / 2.5, 1080 / 2.5) - - -class VideoPlayerWidget(QtGui.QWidget): - - request_frame = QtCore.pyqtSignal(object) - - load_file = QtCore.pyqtSignal(object) - - def __init__(self, parent=None): - super(VideoPlayerWidget, self).__init__(parent) - self.display = DisplayWidget() - self.timeline = QtGui.QScrollBar(Qt.Horizontal) - self.frame_grabber = FrameGrabber() - - self.frame_control = QtGui.QSpinBox() - self.frame_control.setFixedWidth(100) - - self.timeline.valueChanged.connect(self.frame_changed) - self.frame_control.valueChanged.connect(self.frame_changed) - - self.request_frame.connect(self.frame_grabber.request_frame) - self.load_file.connect(self.frame_grabber.set_file) - - self.frame_grabber.frame_ready.connect(self.display.setPixmap) - self.frame_grabber.update_frame_range.connect(self.set_frame_range) - - self.frame_grabber_thread = QtCore.QThread() - - self.frame_grabber.moveToThread(self.frame_grabber_thread) - self.frame_grabber_thread.start() - - control_layout = QtGui.QHBoxLayout() - control_layout.addWidget(self.frame_control) - control_layout.addWidget(self.timeline) - - layout = QtGui.QVBoxLayout() - layout.addWidget(self.display) - layout.addLayout(control_layout) - self.setLayout(layout) - self.setAcceptDrops(True) - - def set_file(self, path): - #self.frame_grabber.set_file(path) - self.load_file.emit(path) - self.frame_changed(0) - - @QtCore.pyqtSlot(object) - def set_frame_range(self, maximum): - print("frame range =", maximum) - self.timeline.setMaximum(maximum) - self.frame_control.setMaximum(maximum) - - def frame_changed(self, value): - self.timeline.blockSignals(True) - self.frame_control.blockSignals(True) - - self.timeline.setValue(value) - self.frame_control.setValue(value) - - self.timeline.blockSignals(False) - self.frame_control.blockSignals(False) - - #self.display.current_index = value - self.frame_grabber.active_frame = value - - self.request_frame.emit(value) - - def keyPressEvent(self, event): - if event.key() in (Qt.Key_Right, Qt.Key_Left): - direction = 1 - if event.key() == Qt.Key_Left: - direction = -1 - - if event.modifiers() == Qt.ShiftModifier: - print('shift') - direction *= 10 - - self.timeline.setValue(self.timeline.value() + direction) - - else: - super(VideoPlayerWidget, self).keyPressEvent(event) - - def mousePressEvent(self, event): - # clear focus of spinbox - focused_widget = QtGui.QApplication.focusWidget() - if focused_widget: - focused_widget.clearFocus() - - super(VideoPlayerWidget, self).mousePressEvent(event) - - def dragEnterEvent(self, event): - event.accept() - - def dropEvent(self, event): - - mime = event.mimeData() - event.accept() - - - if mime.hasUrls(): - path = str(mime.urls()[0].path()) - self.set_file(path) - def closeEvent(self, event): - - self.frame_grabber.active_frame = -1 - self.frame_grabber_thread.quit() - self.frame_grabber_thread.wait() - - event.accept() - - -if __name__ == "__main__": - app = QtGui.QApplication(sys.argv) - window = VideoPlayerWidget() - test_file = sys.argv[1] - window.set_file(test_file) - window.show() - sys.exit(app.exec_()) diff --git a/scratchpad/glproxy.py b/scratchpad/glproxy.py deleted file mode 100644 index b6054e648..000000000 --- a/scratchpad/glproxy.py +++ /dev/null @@ -1,97 +0,0 @@ -'''Mikes wrapper for the visualizer???''' -from contextlib import contextmanager - -from OpenGL.GLUT import * -from OpenGL.GLU import * -from OpenGL.GL import * -import OpenGL - - -__all__ = ''' - gl - glu - glut -'''.strip().split() - - -class ModuleProxy(object): - - def __init__(self, name, module): - self.name = name - self.module = module - - def __getattr__(self, name): - if name.isupper(): - return getattr(self.module, self.name.upper() + '_' + name) - else: - # convert to camel case - name = name.split('_') - name = [x[0].upper() + x[1:] for x in name] - name = ''.join(name) - return getattr(self.module, self.name + name) - - -class GLProxy(ModuleProxy): - - @contextmanager - def matrix(self): - self.module.glPushMatrix() - try: - yield - finally: - self.module.glPopMatrix() - - @contextmanager - def attrib(self, *args): - mask = 0 - for arg in args: - if isinstance(arg, str): - arg = getattr(self.module, 'GL_%s_BIT' % arg.upper()) - mask |= arg - self.module.glPushAttrib(mask) - try: - yield - finally: - self.module.glPopAttrib() - - def enable(self, *args, **kwargs): - self._enable(True, args, kwargs) - return self._apply_on_exit(self._enable, False, args, kwargs) - - def disable(self, *args, **kwargs): - self._enable(False, args, kwargs) - return self._apply_on_exit(self._enable, True, args, kwargs) - - def _enable(self, enable, args, kwargs): - todo = [] - for arg in args: - if isinstance(arg, str): - arg = getattr(self.module, 'GL_%s' % arg.upper()) - todo.append((arg, enable)) - for key, value in kwargs.iteritems(): - flag = getattr(self.module, 'GL_%s' % key.upper()) - value = value if enable else not value - todo.append((flag, value)) - for flag, value in todo: - if value: - self.module.glEnable(flag) - else: - self.module.glDisable(flag) - - def begin(self, arg): - if isinstance(arg, str): - arg = getattr(self.module, 'GL_%s' % arg.upper()) - self.module.glBegin(arg) - return self._apply_on_exit(self.module.glEnd) - - @contextmanager - def _apply_on_exit(self, func, *args, **kwargs): - try: - yield - finally: - func(*args, **kwargs) - - -gl = GLProxy('gl', OpenGL.GL) -glu = ModuleProxy('glu', OpenGL.GLU) -glut = ModuleProxy('glut', OpenGL.GLUT) diff --git a/scratchpad/graph.py b/scratchpad/graph.py deleted file mode 100644 index d1a209c37..000000000 --- a/scratchpad/graph.py +++ /dev/null @@ -1,18 +0,0 @@ -from av.filter.graph import Graph - -g = Graph() -print(g.dump()) - -f = g.pull() - -print(f) - -f = f.reformat(format='rgb24') - -print(f) - -img = f.to_image() - -print(img) - -img.save('graph.png') diff --git a/scratchpad/player.py b/scratchpad/player.py deleted file mode 100644 index e3a7898dc..000000000 --- a/scratchpad/player.py +++ /dev/null @@ -1,100 +0,0 @@ -import argparse -import ctypes -import os -import sys -import pprint -import time - -from qtproxy import Q -from glproxy import gl - -import av - -WIDTH = 960 -HEIGHT = 540 - - -class PlayerGLWidget(Q.GLWidget): - - def initializeGL(self): - print('initialize GL') - gl.clearColor(0, 0, 0, 0) - - gl.enable(gl.TEXTURE_2D) - - # gl.texEnv(gl.TEXTURE_ENV, gl.TEXTURE_ENV_MODE, gl.DECAL) - self.tex_id = gl.genTextures(1) - gl.bindTexture(gl.TEXTURE_2D, self.tex_id) - gl.texParameter(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) - gl.texParameter(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) - - print('texture id', self.tex_id) - - def setImage(self, w, h, img): - gl.texImage2D(gl.TEXTURE_2D, 0, 3, w, h, 0, gl.RGB, gl.UNSIGNED_BYTE, img) - - def resizeGL(self, w, h): - print('resize to', w, h) - gl.viewport(0, 0, w, h) - # gl.matrixMode(gl.PROJECTION) - # gl.loadIdentity() - # gl.ortho(0, w, 0, h, -10, 10) - # gl.matrixMode(gl.MODELVIEW) - - def paintGL(self): - # print 'paint!' - gl.clear(gl.COLOR_BUFFER_BIT) - with gl.begin('polygon'): - gl.texCoord(0, 0); gl.vertex(-1, 1) - gl.texCoord(1, 0); gl.vertex(1, 1) - gl.texCoord(1, 1); gl.vertex(1, -1) - gl.texCoord(0, 1); gl.vertex(-1, -1) - - - -parser = argparse.ArgumentParser() -parser.add_argument('-f', '--format') -parser.add_argument('path') -args = parser.parse_args() - - -def _iter_images(): - video = av.open(args.path, format=args.format) - stream = next(s for s in video.streams if s.type == b'video') - for packet in video.demux(stream): - for frame in packet.decode(): - yield frame.reformat(frame.width, frame.height, 'rgb24') - -image_iter = _iter_images() - -app = Q.Application([]) - -glwidget = PlayerGLWidget() -glwidget.setFixedWidth(WIDTH) -glwidget.setFixedHeight(HEIGHT) -glwidget.show() -glwidget.raise_() - -start_time = 0 -count = 0 - -timer = Q.Timer() -timer.setInterval(1000 / 30) -@timer.timeout.connect -def on_timeout(*args): - - global start_time, count - start_time = start_time or time.time() - - frame = next(image_iter) - ptr = ctypes.c_void_p(frame.planes[0].ptr) - glwidget.setImage(frame.width, frame.height, ptr) - glwidget.updateGL() - - count += 1 - elapsed = time.time() - start_time - print(frame.pts, frame.dts, '%.2ffps' % (count / elapsed)) - -timer.start() - -app.exec_() diff --git a/scratchpad/qtproxy.py b/scratchpad/qtproxy.py deleted file mode 100644 index a39b76fa2..000000000 --- a/scratchpad/qtproxy.py +++ /dev/null @@ -1,22 +0,0 @@ -import sys - -sys.path.append('/usr/local/lib/python2.7/site-packages') -from PyQt4 import QtCore, QtGui, QtOpenGL, QtMultimedia - - -class QtProxy(object): - - def __init__(self, *modules): - self._modules = modules - - def __getattr__(self, base_name): - for mod in self._modules: - for prefix in ('Q', '', 'Qt'): - name = prefix + base_name - obj = getattr(mod, name, None) - if obj is not None: - setattr(self, base_name, obj) - return obj - raise AttributeError(base_name) - -Q = QtProxy(QtGui, QtCore, QtCore.Qt, QtOpenGL, QtMultimedia) diff --git a/scratchpad/remux.py b/scratchpad/remux.py deleted file mode 100644 index 99de07c2c..000000000 --- a/scratchpad/remux.py +++ /dev/null @@ -1,65 +0,0 @@ -import argparse -import logging - -import av - - -logging.basicConfig(level=logging.DEBUG) - - -arg_parser = argparse.ArgumentParser() -arg_parser.add_argument("input") -arg_parser.add_argument("output") -arg_parser.add_argument("-F", "--iformat") -arg_parser.add_argument("-O", "--ioption", action="append", default=[]) -arg_parser.add_argument("-f", "--oformat") -arg_parser.add_argument("-o", "--ooption", action="append", default=[]) -arg_parser.add_argument("-a", "--noaudio", action="store_true") -arg_parser.add_argument("-v", "--novideo", action="store_true") -arg_parser.add_argument("-s", "--nosubs", action="store_true") -arg_parser.add_argument("-d", "--nodata", action="store_true") -arg_parser.add_argument("-c", "--count", type=int, default=0) -args = arg_parser.parse_args() - - -input_ = av.open( - args.input, - format=args.iformat, - options=dict(x.split("=") for x in args.ioption), -) -output = av.open( - args.output, - "w", - format=args.oformat, - options=dict(x.split("=") for x in args.ooption), -) - -in_to_out = {} - -for i, stream in enumerate(input_.streams): - if ( - (stream.type == "audio" and not args.noaudio) - or (stream.type == "video" and not args.novideo) - or (stream.type == "subtitle" and not args.nosubtitle) - or (stream.type == "data" and not args.nodata) - ): - in_to_out[stream] = output.add_stream(template=stream) - -for i, packet in enumerate(input_.demux(list(in_to_out.keys()))): - - if args.count and i >= args.count: - break - print("%02d %r" % (i, packet)) - print("\tin: ", packet.stream) - - if packet.dts is None: - continue - - packet.stream = in_to_out[packet.stream] - - print("\tout:", packet.stream) - - output.mux(packet) - - -output.close() diff --git a/scratchpad/resource_use.py b/scratchpad/resource_use.py deleted file mode 100644 index 3387b0d70..000000000 --- a/scratchpad/resource_use.py +++ /dev/null @@ -1,61 +0,0 @@ -import argparse -import resource -import gc - -import av - - -parser = argparse.ArgumentParser() -parser.add_argument('-c', '--count', type=int, default=5) -parser.add_argument('-f', '--frames', type=int, default=100) -parser.add_argument('--print', dest='print_', action='store_true') -parser.add_argument('--to-rgb', action='store_true') -parser.add_argument('--to-image', action='store_true') -parser.add_argument('--gc', '-g', action='store_true') -parser.add_argument('input') -args = parser.parse_args() - -def format_bytes(n): - order = 0 - while n > 1024: - order += 1 - n //= 1024 - return '%d%sB' % (n, ('', 'k', 'M', 'G', 'T', 'P')[order]) - -usage = [] - -for round_ in range(args.count): - - print('Round %d/%d:' % (round_ + 1, args.count)) - - if args.gc: - gc.collect() - - usage.append(resource.getrusage(resource.RUSAGE_SELF)) - - fh = av.open(args.input) - vs = next(s for s in fh.streams if s.type == 'video') - - fi = 0 - for packet in fh.demux([vs]): - for frame in packet.decode(): - if args.print_: - print(frame) - if args.to_rgb: - print(frame.to_rgb()) - if args.to_image: - print(frame.to_image()) - fi += 1 - if fi > args.frames: - break - - frame = packet = fh = vs = None - - - -usage.append(resource.getrusage(resource.RUSAGE_SELF)) - -for i in range(len(usage) - 1): - before = usage[i] - after = usage[i + 1] - print('%s (%s)' % (format_bytes(after.ru_maxrss), format_bytes(after.ru_maxrss - before.ru_maxrss))) diff --git a/scratchpad/save_subtitles.py b/scratchpad/save_subtitles.py deleted file mode 100644 index 8666501d8..000000000 --- a/scratchpad/save_subtitles.py +++ /dev/null @@ -1,61 +0,0 @@ -""" - -As you can see, the subtitle API needs some work. - -""" - -import os -import sys -import pprint - -from PIL import Image - -from av import open - - -if not os.path.exists('subtitles'): - os.makedirs('subtitles') - - -video = open(sys.argv[1]) - -streams = [s for s in video.streams if s.type == b'subtitle'] -if not streams: - print('no subtitles') - exit(1) - -print(streams) - -count = 0 -for pi, packet in enumerate(video.demux([streams[0]])): - - print('packet', pi) - for si, subtitle in enumerate(packet.decode()): - - print('\tsubtitle', si, subtitle) - - for ri, rect in enumerate(subtitle.rects): - if rect.type == 'ass': - print('\t\tass: ', rect, rect.ass.rstrip('\n')) - if rect.type == 'text': - print('\t\ttext: ', rect, rect.text.rstrip('\n')) - if rect.type == 'bitmap': - print('\t\tbitmap: ', rect, rect.width, rect.height, rect.pict_buffers) - buffers = [b for b in rect.pict_buffers if b is not None] - if buffers: - imgs = [ - Image.frombuffer('L', (rect.width, rect.height), buffer, "raw", "L", 0, 1) - for buffer in buffers - ] - if len(imgs) == 1: - img = imgs[0] - elif len(imgs) == 2: - img = Image.merge('LA', imgs) - else: - img = Image.merge('RGBA', imgs) - img.save('subtitles/%04d.png' % count) - - count += 1 - if count > 10: - pass - # exit() diff --git a/scratchpad/second_seek_example.py b/scratchpad/second_seek_example.py deleted file mode 100644 index 58b3c3811..000000000 --- a/scratchpad/second_seek_example.py +++ /dev/null @@ -1,499 +0,0 @@ -""" -Note this example only really works accurately on constant frame rate media. -""" -from PyQt4 import QtGui -from PyQt4 import QtCore -from PyQt4.QtCore import Qt - -import sys -import av - - -AV_TIME_BASE = 1000000 - -def pts_to_frame(pts, time_base, frame_rate, start_time): - return int(pts * time_base * frame_rate) - int(start_time * time_base * frame_rate) - -def get_frame_rate(stream): - - if stream.average_rate.denominator and stream.average_rate.numerator: - return float(stream.average_rate) - if stream.time_base.denominator and stream.time_base.numerator: - return 1.0 / float(stream.time_base) - else: - raise ValueError("Unable to determine FPS") - -def get_frame_count(f, stream): - - if stream.frames: - return stream.frames - elif stream.duration: - return pts_to_frame(stream.duration, float(stream.time_base), get_frame_rate(stream), 0) - elif f.duration: - return pts_to_frame(f.duration, 1 / float(AV_TIME_BASE), get_frame_rate(stream), 0) - - else: - raise ValueError("Unable to determine number for frames") - -class FrameGrabber(QtCore.QObject): - - frame_ready = QtCore.pyqtSignal(object, object) - update_frame_range = QtCore.pyqtSignal(object, object) - - def __init__(self, parent=None): - super(FrameGrabber, self).__init__(parent) - self.file = None - self.stream = None - self.frame = None - self.active_time = None - self.start_time = 0 - self.pts_seen = False - self.nb_frames = None - - self.rate = None - self.time_base = None - - self.pts_map = {} - - def next_frame(self): - - frame_index = None - - rate = self.rate - time_base = self.time_base - - self.pts_seen = False - - for packet in self.file.demux(self.stream): - #print " pkt", packet.pts, packet.dts, packet - if packet.pts: - self.pts_seen = True - - for frame in packet.decode(): - - if frame_index is None: - - if self.pts_seen: - pts = frame.pts - else: - pts = frame.dts - - if not pts is None: - frame_index = pts_to_frame(pts, time_base, rate, self.start_time) - - elif not frame_index is None: - frame_index += 1 - - if not frame.dts in self.pts_map: - secs = None - - if not pts is None: - secs = pts * time_base - - self.pts_map[frame.dts] = secs - - - #if frame.pts == None: - - - - yield frame_index, frame - - - - @QtCore.pyqtSlot(object) - def request_time(self, second): - - frame = self.get_frame(second) - if not frame: - return - - rgba = frame.reformat(frame.width, frame.height, "rgb24", 'itu709') - #print rgba.to_image().save("test.png") - # could use the buffer interface here instead, some versions of PyQt don't support it for some reason - # need to track down which version they added support for it - self.frame = bytearray(rgba.planes[0]) - bytesPerPixel = 3 - img = QtGui.QImage(self.frame, rgba.width, rgba.height, rgba.width * bytesPerPixel, QtGui.QImage.Format_RGB888) - - #img = QtGui.QImage(rgba.planes[0], rgba.width, rgba.height, QtGui.QImage.Format_RGB888) - - #pixmap = QtGui.QPixmap.fromImage(img) - self.frame_ready.emit(img, second) - - def get_frame(self, target_sec): - - if target_sec != self.active_time: - return - print('seeking to', target_sec) - - rate = self.rate - time_base = self.time_base - - target_pts = int(target_sec / time_base) + self.start_time - seek_pts = target_pts - - - self.stream.seek(seek_pts) - - #frame_cache = [] - - last_frame = None - - for i, (frame_index, frame) in enumerate(self.next_frame()): - - - if target_sec != self.active_time: - return - - pts = frame.dts - if self.pts_seen: - pts = frame.pts - - if pts > target_pts: - break - - print(frame.pts, seek_pts) - last_frame = frame - - if last_frame: - - return last_frame - - - def get_frame_old(self, target_frame): - - if target_frame != self.active_frame: - return - print('seeking to', target_frame) - - seek_frame = target_frame - - rate = self.rate - time_base = self.time_base - - frame = None - reseek = 250 - - original_target_frame_pts = None - - while reseek >= 0: - - # convert seek_frame to pts - target_sec = seek_frame * 1 / rate - target_pts = int(target_sec / time_base) + self.start_time - - if original_target_frame_pts is None: - original_target_frame_pts = target_pts - - self.stream.seek(int(target_pts)) - - frame_index = None - - frame_cache = [] - - for i, (frame_index, frame) in enumerate(self.next_frame()): - - # optimization if the time slider has changed, the requested frame no longer valid - if target_frame != self.active_frame: - return - - print(" ", i, "at frame", frame_index, "at ts:", frame.pts, frame.dts, "target:", target_pts, 'orig', original_target_frame_pts) - - if frame_index is None: - pass - - elif frame_index >= target_frame: - break - - frame_cache.append(frame) - - # Check if we over seeked, if we over seekd we need to seek to a earlier time - # but still looking for the target frame - if frame_index != target_frame: - - if frame_index is None: - over_seek = '?' - else: - over_seek = frame_index - target_frame - if frame_index > target_frame: - - print(over_seek, frame_cache) - if over_seek <= len(frame_cache): - print("over seeked by %i, using cache" % over_seek) - frame = frame_cache[-over_seek] - break - - - seek_frame -= 1 - reseek -= 1 - print("over seeked by %s, backtracking.. seeking: %i target: %i retry: %i" % (str(over_seek), seek_frame, target_frame, reseek)) - - else: - break - - if reseek < 0: - raise ValueError("seeking failed %i" % frame_index) - - # frame at this point should be the correct frame - - if frame: - - return frame - - else: - raise ValueError("seeking failed %i" % target_frame) - - def get_frame_count(self): - - frame_count = None - - if self.stream.frames: - frame_count = self.stream.frames - elif self.stream.duration: - frame_count = pts_to_frame(self.stream.duration, float(self.stream.time_base), get_frame_rate(self.stream), 0) - elif self.file.duration: - frame_count = pts_to_frame(self.file.duration, 1 / float(AV_TIME_BASE), get_frame_rate(self.stream), 0) - else: - raise ValueError("Unable to determine number for frames") - - seek_frame = frame_count - - retry = 100 - - while retry: - target_sec = seek_frame * 1 / self.rate - target_pts = int(target_sec / self.time_base) + self.start_time - - self.stream.seek(int(target_pts)) - - frame_index = None - - for frame_index, frame in self.next_frame(): - print(frame_index, frame) - continue - - if not frame_index is None: - break - else: - seek_frame -= 1 - retry -= 1 - - - print("frame count seeked", frame_index, "container frame count", frame_count) - - return frame_index or frame_count - - @QtCore.pyqtSlot(object) - def set_file(self, path): - self.file = av.open(path) - self.stream = next(s for s in self.file.streams if s.type == b'video') - self.rate = get_frame_rate(self.stream) - self.time_base = float(self.stream.time_base) - - - index, first_frame = next(self.next_frame()) - self.stream.seek(self.stream.start_time) - - # find the pts of the first frame - index, first_frame = next(self.next_frame()) - - if self.pts_seen: - pts = first_frame.pts - else: - pts = first_frame.dts - - self.start_time = pts or first_frame.dts - - print("First pts", pts, self.stream.start_time, first_frame) - - #self.nb_frames = get_frame_count(self.file, self.stream) - self.nb_frames = self.get_frame_count() - - dur = None - - if self.stream.duration: - dur = self.stream.duration * self.time_base - else: - dur = self.file.duration * 1.0 / float(AV_TIME_BASE) - - self.update_frame_range.emit(dur, self.rate) - - - - - -class DisplayWidget(QtGui.QLabel): - def __init__(self, parent=None): - super(DisplayWidget, self).__init__(parent) - #self.setScaledContents(True) - self.setMinimumSize(1920 / 10, 1080 / 10) - - size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) - size_policy.setHeightForWidth(True) - - self.setSizePolicy(size_policy) - - self.setAlignment(Qt.AlignHCenter | Qt.AlignBottom) - - self.pixmap = None - self.setMargin(10) - - def heightForWidth(self, width): - return width * 9 / 16.0 - - @QtCore.pyqtSlot(object, object) - def setPixmap(self, img, index): - #if index == self.current_index: - self.pixmap = QtGui.QPixmap.fromImage(img) - - #super(DisplayWidget, self).setPixmap(self.pixmap) - super(DisplayWidget, self).setPixmap(self.pixmap.scaled(self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) - - def sizeHint(self): - width = self.width() - return QtCore.QSize(width, self.heightForWidth(width)) - - def resizeEvent(self, event): - if self.pixmap: - super(DisplayWidget, self).setPixmap(self.pixmap.scaled(self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) - - def sizeHint(self): - return QtCore.QSize(1920 / 2.5, 1080 / 2.5) - - -class VideoPlayerWidget(QtGui.QWidget): - - request_time = QtCore.pyqtSignal(object) - - load_file = QtCore.pyqtSignal(object) - - def __init__(self, parent=None): - super(VideoPlayerWidget, self).__init__(parent) - - self.rate = None - - self.display = DisplayWidget() - self.timeline = QtGui.QScrollBar(Qt.Horizontal) - self.timeline_base = 100000 - - self.frame_grabber = FrameGrabber() - - self.frame_control = QtGui.QDoubleSpinBox() - self.frame_control.setFixedWidth(100) - - self.timeline.valueChanged.connect(self.slider_changed) - self.frame_control.valueChanged.connect(self.frame_changed) - - self.request_time.connect(self.frame_grabber.request_time) - self.load_file.connect(self.frame_grabber.set_file) - - self.frame_grabber.frame_ready.connect(self.display.setPixmap) - self.frame_grabber.update_frame_range.connect(self.set_frame_range) - - self.frame_grabber_thread = QtCore.QThread() - - self.frame_grabber.moveToThread(self.frame_grabber_thread) - self.frame_grabber_thread.start() - - control_layout = QtGui.QHBoxLayout() - control_layout.addWidget(self.frame_control) - control_layout.addWidget(self.timeline) - - layout = QtGui.QVBoxLayout() - layout.addWidget(self.display) - layout.addLayout(control_layout) - self.setLayout(layout) - self.setAcceptDrops(True) - - def set_file(self, path): - #self.frame_grabber.set_file(path) - self.load_file.emit(path) - self.frame_changed(0) - - @QtCore.pyqtSlot(object, object) - def set_frame_range(self, maximum, rate): - print("frame range =", maximum, rate, int(maximum * self.timeline_base)) - - self.timeline.setMaximum(int(maximum * self.timeline_base)) - - self.frame_control.setMaximum(maximum) - self.frame_control.setSingleStep(1 / rate) - #self.timeline.setSingleStep( int(AV_TIME_BASE * 1/rate)) - self.rate = rate - - def slider_changed(self, value): - print('..', value) - self.frame_changed(value * 1.0 / float(self.timeline_base)) - - def frame_changed(self, value): - self.timeline.blockSignals(True) - self.frame_control.blockSignals(True) - - self.timeline.setValue(int(value * self.timeline_base)) - self.frame_control.setValue(value) - - self.timeline.blockSignals(False) - self.frame_control.blockSignals(False) - - #self.display.current_index = value - self.frame_grabber.active_time = value - - self.request_time.emit(value) - - def keyPressEvent(self, event): - if event.key() in (Qt.Key_Right, Qt.Key_Left): - direction = 1 - if event.key() == Qt.Key_Left: - direction = -1 - - if event.modifiers() == Qt.ShiftModifier: - print('shift') - direction *= 10 - - direction = direction * 1 / self.rate - - self.frame_changed(self.frame_control.value() + direction) - - else: - super(VideoPlayerWidget, self).keyPressEvent(event) - - def mousePressEvent(self, event): - # clear focus of spinbox - focused_widget = QtGui.QApplication.focusWidget() - if focused_widget: - focused_widget.clearFocus() - - super(VideoPlayerWidget, self).mousePressEvent(event) - - def dragEnterEvent(self, event): - event.accept() - - def dropEvent(self, event): - - mime = event.mimeData() - event.accept() - - - if mime.hasUrls(): - path = str(mime.urls()[0].path()) - self.set_file(path) - def closeEvent(self, event): - - self.frame_grabber.active_time = -1 - self.frame_grabber_thread.quit() - self.frame_grabber_thread.wait() - - for key, value in sorted(self.frame_grabber.pts_map.items()): - print(key, '=', value) - - event.accept() - - -if __name__ == "__main__": - app = QtGui.QApplication(sys.argv) - window = VideoPlayerWidget() - test_file = sys.argv[1] - window.set_file(test_file) - window.show() - sys.exit(app.exec_()) diff --git a/scratchpad/seekmany.py b/scratchpad/seekmany.py deleted file mode 100644 index 0b37e5118..000000000 --- a/scratchpad/seekmany.py +++ /dev/null @@ -1,49 +0,0 @@ -import sys - -import av - -container = av.open(sys.argv[1]) -duration = container.duration -stream = container.streams.video[0] - -print('container.duration', duration, float(duration) / av.time_base) -print('container.time_base', av.time_base) -print('stream.duration', stream.duration) -print('stream.time_base', stream.time_base) -print('codec.time_base', stream.codec_context.time_base) -print('scale', float(stream.codec_context.time_base / stream.time_base)) -print() - -exit() - -real_duration = float(duration) / av.time_base -steps = 120 -tolerance = real_duration / (steps * 4) -print('real_duration', real_duration) -print() - -def iter_frames(): - for packet in container.demux(stream): - for frame in packet.decode(): - yield frame - -for i in range(steps): - - time = real_duration * i / steps - min_time = time - tolerance - - pts = time / stream.time_base - - print('seeking', time, pts) - stream.seek(int(pts)) - - skipped = 0 - for frame in iter_frames(): - ftime = float(frame.pts * stream.time_base) - if ftime >= min_time: - break - skipped += 1 - else: - print(' WARNING: iterated to the end') - - print(' ', skipped, frame.pts, float(frame.pts * stream.time_base)) # WTF is this stream.time_base? diff --git a/scratchpad/show_frames_opencv.py b/scratchpad/show_frames_opencv.py deleted file mode 100644 index c618afaac..000000000 --- a/scratchpad/show_frames_opencv.py +++ /dev/null @@ -1,21 +0,0 @@ -import os -import sys - -import cv2 -from av import open - - -video = open(sys.argv[1]) - -stream = next(s for s in video.streams if s.type == 'video') - -for packet in video.demux(stream): - for frame in packet.decode(): - # some other formats gray16be, bgr24, rgb24 - img = frame.to_ndarray(format='bgr24') - cv2.imshow("Test", img) - - if cv2.waitKey(1) == 27: - break - -cv2.destroyAllWindows() diff --git a/scratchpad/sidedata.py b/scratchpad/sidedata.py deleted file mode 100644 index a1157d7dd..000000000 --- a/scratchpad/sidedata.py +++ /dev/null @@ -1,25 +0,0 @@ -import sys - -import av - - -fh = av.open(sys.argv[1]) -fh.streams.video[0].export_mvs = True -# fh.streams.video[0].flags2 |= 'EXPORT_MVS' - -for pi, packet in enumerate(fh.demux()): - for fi, frame in enumerate(packet.decode()): - - for di, data in enumerate(frame.side_data): - - print(pi, fi, di, data) - - print(data.to_ndarray()) - - for mi, vec in enumerate(data): - - print(mi, vec) - - if mi > 10: - exit() -