import * as std from "std"; | |||||
function usage(msg) | |||||
{ | |||||
if ( msg ) | |||||
print(msg); | |||||
print("usage: ./qjs " + scriptArgs[0] + " [-p] -i <input.json> -s <script.js> -o <output.json>"); | |||||
return 1; | |||||
} | |||||
//--------------------------------------------------------------------- | |||||
// no lf, no space | |||||
const pflags_def = [ [ false, false ] ]; | |||||
function get_pflags(jso) | |||||
{ | |||||
if ( jso === "fcode" || jso === "bcode" ) | |||||
{ | |||||
return [ | |||||
[ true, false ] | |||||
]; | |||||
} | |||||
else if ( jso === "forward" || jso === "backward" ) | |||||
{ | |||||
return [ | |||||
[ false, false ], | |||||
[ true, false ], | |||||
[ true, true ] | |||||
]; | |||||
} | |||||
return pflags_def; | |||||
} | |||||
function print_lf(o_file, level) | |||||
{ | |||||
o_file.puts('\n'); | |||||
o_file.printf("%*s", level * 2, ""); | |||||
} | |||||
function print_space(o_file) | |||||
{ | |||||
o_file.puts(' '); | |||||
} | |||||
function output_lf(o_file, pflags, level) | |||||
{ | |||||
const json_pflags_no_lf = pflags[0]; | |||||
const json_pflags_no_space = pflags[1]; | |||||
if ( !json_pflags_no_lf ) | |||||
print_lf(o_file, level); | |||||
else if ( !json_pflags_no_space ) | |||||
print_space(o_file); | |||||
} | |||||
function json_print_element(o_file, jso, pflags, level) | |||||
{ | |||||
if ( typeof jso === "object" ) | |||||
{ | |||||
if ( jso === null ) | |||||
{ | |||||
o_file.puts("null"); | |||||
} | |||||
else if ( Array.isArray(jso) ) | |||||
{ | |||||
const pflags0 = pflags[0]; | |||||
const pflags1 = pflags.slice(1); | |||||
o_file.puts('['); | |||||
for ( let i = 0; i < jso.length; i++ ) | |||||
{ | |||||
if ( i != 0 ) | |||||
o_file.puts(','); | |||||
output_lf(o_file, pflags0, level+1); | |||||
json_print_element(o_file, jso[i], pflags1, level+1); | |||||
} | |||||
output_lf(o_file, pflags0, level); | |||||
o_file.puts(']'); | |||||
} | |||||
else | |||||
{ | |||||
let i = 0; | |||||
o_file.puts('{'); | |||||
for ( const key in jso ) | |||||
{ | |||||
if ( i++ != 0 ) | |||||
o_file.puts(','); | |||||
print_lf(o_file, level+1); | |||||
o_file.puts(JSON.stringify(key)); | |||||
o_file.puts(':'); | |||||
json_print_element(o_file, jso[key], get_pflags(key), level+1); | |||||
} | |||||
print_lf(o_file, level); | |||||
o_file.puts('}'); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
o_file.puts(JSON.stringify(jso)); | |||||
} | |||||
} | |||||
function json_fputs(o_file, jso) | |||||
{ | |||||
json_print_element(o_file, jso, pflags_def, 0); | |||||
o_file.puts('\n'); | |||||
} | |||||
/////////////////////////////////////////////////////////////////////// | |||||
// global variables for the script | |||||
var json_stream = null; | |||||
var json_frame = null; | |||||
function run_script(s_path, i_path, o_path, do_pretty) | |||||
{ | |||||
// load input JSON | |||||
print("[+] loading input JSON file '" + i_path + "'") | |||||
let i_file = std.open(i_path, "r"); | |||||
if ( !i_file ) | |||||
return usage("could not open input file '" + i_path + "'"); | |||||
let str = i_file.readAsString(); | |||||
let json_root = JSON.parse(str); | |||||
if ( !json_root ) | |||||
return usage("error parsing input file '" + i_path + "'"); | |||||
i_file.close(); | |||||
// will error out on exception | |||||
print("[+] running script '" + s_path + "' on JSON data") | |||||
std.loadScript(s_path); | |||||
// for each stream (normally there is only one stream of interest) | |||||
let streams = json_root["streams"]; | |||||
for ( let i = 0; i < streams.length; i++ ) | |||||
{ | |||||
let stream = streams[i]; | |||||
json_stream = stream; | |||||
// for each frame in the stream | |||||
let frames = stream["frames"]; | |||||
for ( let j = 0; j < frames.length; j++ ) | |||||
{ | |||||
let frame = frames[j]; | |||||
json_frame = frame; | |||||
glitch_frame(frame); | |||||
} | |||||
} | |||||
// open input JSON | |||||
print("[+] writing " + (do_pretty ? "prettified " : "") + "output JSON file '" + o_path + "'") | |||||
let o_file = std.open(o_path, "w+"); | |||||
if ( !o_file ) | |||||
return usage("could not open output file '" + o_path + "'"); | |||||
if ( do_pretty ) | |||||
json_fputs(o_file, json_root); | |||||
else | |||||
o_file.puts(JSON.stringify(json_root)); | |||||
o_file.close(); | |||||
return 0; | |||||
} | |||||
function main(argc, argv) | |||||
{ | |||||
let i_path = null; | |||||
let o_path = null; | |||||
let s_path = null; | |||||
let do_pretty = false; | |||||
for ( let i = 1; i < argc; ) | |||||
{ | |||||
let opt = argv[i++]; | |||||
if ( opt == "-i" ) | |||||
{ | |||||
if ( i == argc ) | |||||
return usage("parameter missing for option '-i'"); | |||||
i_path = argv[i++]; | |||||
} | |||||
else if ( opt == "-o" ) | |||||
{ | |||||
if ( i == argc ) | |||||
return usage("parameter missing for option '-o'"); | |||||
o_path = argv[i++]; | |||||
} | |||||
else if ( opt == "-s" ) | |||||
{ | |||||
if ( i == argc ) | |||||
return usage("parameter missing for option '-s'"); | |||||
s_path = argv[i++]; | |||||
} | |||||
else if ( opt == "-p" ) | |||||
{ | |||||
do_pretty = true; | |||||
} | |||||
else | |||||
{ | |||||
return usage("unknown option '" + opt + "'"); | |||||
} | |||||
} | |||||
if ( !s_path || !i_path || !o_path ) | |||||
return usage(); | |||||
return run_script(s_path, i_path, o_path, do_pretty); | |||||
} | |||||
main(scriptArgs.length, scriptArgs, this); |
#!/usr/bin/env python | |||||
# run script on frames from input file: | |||||
# ffglitch -i <file> -f <features> -s <script> -o <file> | |||||
import argparse | |||||
import os | |||||
import subprocess | |||||
import sys | |||||
import tempfile | |||||
import hashlib | |||||
# Load json library (try first with fastest one) | |||||
try: | |||||
import orjson | |||||
_json = orjson | |||||
_dumps_ftype = 'wb' | |||||
except: | |||||
import json | |||||
_json = json | |||||
_dumps_ftype = 'w' | |||||
parser = argparse.ArgumentParser() | |||||
parser.add_argument('-i', metavar="<file>", dest='input', required=True, help='input media file') | |||||
parser.add_argument('-f', metavar="<feature>", dest='feature', required=True, help='select feature to glitch') | |||||
parser.add_argument('-s', metavar="<file>", dest='script', required=True, help='script to glitch frames') | |||||
parser.add_argument('-o', metavar="<file>", dest='output', required=True, help='output media file') | |||||
parser.add_argument('-v', action="store_true", dest='verbose', required=False, help='verbose output') | |||||
parser.add_argument('-k', action="store_true", dest='keep', required=False, help='do not delete temporary JSON file') | |||||
args = parser.parse_args() | |||||
# Handle verbosity | |||||
if args.verbose: | |||||
stderr = sys.stdout | |||||
else: | |||||
stderr = open(os.devnull, 'w') | |||||
# Generate input json file name | |||||
json_in = os.path.splitext(args.input)[0] + ".json" | |||||
# Check that input file name does not end in .json | |||||
if json_in == args.input: | |||||
raise ValueError('Input file name must not end in .json') | |||||
# Function to get sha1sum of file | |||||
def calc_sha1sum(filename): | |||||
h = hashlib.sha1() | |||||
b = bytearray(128*1024) | |||||
mv = memoryview(b) | |||||
with open(filename, 'rb', buffering=0) as f: | |||||
for n in iter(lambda : f.readinto(mv), 0): | |||||
h.update(mv[:n]) | |||||
return h.hexdigest() | |||||
# Try to read input json file | |||||
json_root = None | |||||
try: | |||||
with open(json_in, 'r') as f: | |||||
print("[+] checking existing JSON file '%s'" % json_in) | |||||
json_root = _json.loads(f.read()) | |||||
# check features | |||||
features = json_root["features"] | |||||
if len(features) != 1 or features[0] != args.feature: | |||||
json_root = None | |||||
print("[-] feature '%s' not found in '%s'" % (args.feature, json_in)) | |||||
# check sha1sum | |||||
sha1sum = json_root["sha1sum"] | |||||
if len(sha1sum) != 40 or sha1sum != calc_sha1sum(args.input): | |||||
json_root = None | |||||
print("[-] sha1sum mismatch for '%s' in '%s'" % (args.input, json_in)) | |||||
if json_root is not None: | |||||
print("[+] OK") | |||||
except IOError: | |||||
pass | |||||
ffedit_path = None | |||||
def run_ffedit(cmd): | |||||
global ffedit_path | |||||
if ffedit_path == None: | |||||
dir_path = os.path.dirname(os.path.realpath(__file__)) | |||||
ffedit_path = [os.path.join(dir_path, "ffedit")] | |||||
cmd = ffedit_path + cmd | |||||
if args.verbose: | |||||
print("[v] $ %s" % ' '.join(cmd)) | |||||
return subprocess.check_output(cmd, stderr=stderr) | |||||
# First pass (export data) | |||||
if json_root is None: | |||||
print("[+] exporting feature '%s' to '%s'" % (args.feature, json_in)) | |||||
run_ffedit([args.input, "-f", args.feature, "-e", json_in]) | |||||
with open(json_in, 'r') as f: | |||||
json_root = _json.loads(f.read()) | |||||
# Run script on JSON data. | |||||
print("[+] running script '%s' on JSON data" % args.script) | |||||
with open(args.script) as infile: | |||||
exec(infile.read()) | |||||
json_stream = None | |||||
json_frame = None | |||||
# for each stream (normally there is only one stream of interest) | |||||
for stream in json_root["streams"]: | |||||
json_stream = stream | |||||
# for each frame in the stream | |||||
for frame in stream["frames"]: | |||||
json_frame = frame | |||||
# if ffedit exported motion vectors | |||||
if args.feature in frame: | |||||
glitch_frame(frame[args.feature]) | |||||
# Dump modified JSON data to temporary file. | |||||
json_fd, json_out = tempfile.mkstemp(prefix='ffglitch_', suffix='.json') | |||||
json_fp = os.fdopen(json_fd, _dumps_ftype) | |||||
print("[+] dumping modified data to '%s'" % json_out) | |||||
json_fp.write(_json.dumps(json_root)) | |||||
json_fp.close() | |||||
# Second pass (apply data). | |||||
print("[+] applying modified data to '%s'" % args.output) | |||||
ok = False | |||||
try: | |||||
run_ffedit([args.input, "-f", args.feature, "-a", json_out, args.output]) | |||||
ok = True | |||||
except subprocess.CalledProcessError as grepexc: | |||||
vstr = " (rerun FFglitch with '-v')" if not args.verbose else "" | |||||
print("[-] something went wrong%s" % vstr) | |||||
# Remove temporary JSON file. | |||||
if ok and not args.keep: | |||||
os.unlink(json_out) | |||||
else: | |||||
print("[+] not deleting temporary file '%s'" % json_out) |
<Technical info about this build>: | |||||
FFglitch 0.9.4 | |||||
macOS | |||||
64-bit | |||||
You can get the source code here: | |||||
http://ffglitch.org/pub/src/ffglitch-0.9.4.tar.xz | |||||
FFglitch also includes the following libraries: | |||||
quickjs 2021-03-27 <https://bellard.org/quickjs> | |||||
zlib 1.2.11 <http://zlib.net> | |||||
Xvid 1.3.6 <https://labs.xvid.com> | |||||
<What?> | |||||
FFglitch is a multimedia bitstream editor, based on the open-source | |||||
project FFmpeg <http://ffmpeg.org>. | |||||
For more information, go to <http://ffglitch.org>. | |||||
<Who?> | |||||
Hi, I’m Ramiro Polla <https://github.com/ramiropolla>. | |||||
On my free time I like to work on useless projects. | |||||
<GPL boilerplate>: | |||||
Copyright (C) 2018-2021 Ramiro Polla | |||||
This program is free software; you can redistribute it and/or | |||||
modify it under the terms of the GNU General Public License | |||||
as published by the Free Software Foundation; either version 2 | |||||
of the License, or (at your option) any later version. | |||||
This program is distributed in the hope that it will be useful, | |||||
but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
GNU General Public License for more details. | |||||
You should have received a copy of the GNU General Public License | |||||
along with this program; if not, write to the Free Software | |||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |