Skip to content

Commit

Permalink
alignment mode (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
neri14 authored Aug 31, 2024
1 parent 9b8cf3d commit 70190b3
Show file tree
Hide file tree
Showing 30 changed files with 726 additions and 651 deletions.
13 changes: 4 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

example ffmpeg command: ```ffmpeg -ss 00:00:30.0 -to 00:01:00.0 -i input.mp4 -c copy output.mp4```

1. Run alignment tool and figure out offset (tool instruction below; functionality to be integrated into main generator https://github.com/neri14/videographer/issues/21)
1. Run generator in alignment mode (see instructions below) to determine telemetry offset

1. Run generator app to generate final video

Expand All @@ -53,16 +53,11 @@ Packages required in system to build the application
- pugixml


## Alignment Tool

Python alignment tool is located in tools/alignment.
## Alignment Mode

Basic usage based on speed visible on video:

1. Create and enter virtualenv
1. Install requirements from requirements.txt
1. determine rough clip ```START``` and ```DURATION``` that will havevisible changes in speed on bike computer (within first 30s of FIT file track)
1. run ```python align.py -b <START> -t <DURATION> path_to_fit_file.fit path_to_video.mp4``` to generate alignment clip
1. open video with your favorite video player and find exact frame at which speed changes
1. run generator with ```--alignment``` to generate alignment clip (can also use ```--clip-time``` to change default 60s duration of alignment clip)
1. open video in your favorite video player and find exact frame at which speed changes
1. locate data frame with same speed change in alignment data and remember the ```OFFSET```
1. use that ```OFFSET``` as input for main application to align the overlay
208 changes: 122 additions & 86 deletions src/arguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,86 +2,87 @@
#include "utils/argument_parser.h"

namespace vgraph {
namespace consts {
int defult_alignment_clip_time(60);
}
namespace key {
std::string help("help");
std::string debug("debug");
std::string gpu("gpu");
std::string timecode("timecode");
std::string alignment("alignment");
std::string telemetry("telemetry");
std::string layout("layout");
std::string offset("offset");
std::string input("input");
std::string output("output");
std::string timecode("timecode");
std::string resolution("resolution");
std::string bitrate("bitrate");
std::string clip_time("clip_time");
}

utils::argument_parser prepare_parser()
{
utils::argument_parser parser("vgraph");

parser.add_argument(key::help, utils::argument().flag().option("-h").option("--help").description("Print this help message"));
parser.add_argument(key::debug, utils::argument().flag().option("-d").option("--debug").description("Enable debug logs"));
parser.add_argument(key::gpu, utils::argument().flag().option("-g").option("--gpu").description("Use Nvidia GPU for processing"));
/* helpers forward declarations */
utils::argument_parser prepare_parser();

parser.add_argument(key::telemetry, utils::argument().option("-t").option("--telemetry").description("Telemetry file path"));
parser.add_argument(key::layout, utils::argument().option("-a").option("--layout").description("Layout file path"));
parser.add_argument(key::input, utils::argument().option("-i").option("--input").description("Input video file path"));
parser.add_argument(key::output, utils::argument().option("-o").option("--output").description("Output video file path"));

parser.add_argument(key::offset, utils::argument().option("-s").option("--offset").description("Telemetry offset in seconds (video time at which track starts)"));
parser.add_argument(key::timecode, utils::argument().flag().option("-c").option("--timecode").description("Draw a timecode on each frame"));
arguments read_args(const utils::argument_parser& parser, utils::logging::logger& log);
void assert_arguments_valid(const arguments& args, utils::logging::logger& log);

parser.add_argument(key::resolution, utils::argument().option("-r").option("--resolution").description("Output video resolution, format: WIDTHxHEIGHT"));
parser.add_argument(key::bitrate, utils::argument().option("-b").option("--bitrate").description("Output video bitrate, in kbit/s"));
template <typename T>
bool read_value(const utils::argument_parser& parser, const std::string& name, utils::logging::logger& log, std::optional<T>& value_out);

return parser;
}
bool parse_resolution(const std::string& str, utils::logging::logger& log, std::optional<std::pair<int, int>>& out);

template <typename T>
bool read_mandatory_value(const utils::argument_parser& parser, const std::string& name, utils::logging::logger& log, T& value_out)
/* interface */
arguments arguments::parse(int argc, char* argv[])
{
if (!parser.has(name)) {
log.error("Missing mandatory {} argument", name);
return false;
}
utils::logging::logger log{"arguments::parse()"};

if (parser.count(name) > 1) {
log.error("Expected 1 value for {} got {}", name, parser.count(name));
return false;
}
try {
auto parser = prepare_parser();

value_out = parser.get<T>(name);
return true;
}
parser.parse(argc, argv);
if (parser.get<bool>(key::help)) {
parser.print_help();
exit(0); //ok case
}

template <typename T>
bool read_optional_value(const utils::argument_parser& parser, const std::string& name, utils::logging::logger& log, std::optional<T>& value_out)
{
if (!parser.has(name)) {
return true;
}
auto args =read_args(parser, log);
assert_arguments_valid(args, log);
return std::move(args);

if (parser.count(name) > 1) {
log.error("Expected 1 value for {} got {}", name, parser.count(name));
return false;
} catch (utils::argument_exception e) {
log.error(e.what());
exit(1);
} catch (std::exception e) {
log.error("Unexpected error: {}", e.what());
exit(1);
}

value_out = parser.get<T>(name);
return true;
//shall never reach
log.error("Execution error");
exit(2);
}

bool parse_resolution(const std::string& str, utils::logging::logger& log, std::pair<int, int>& out)
/* helpers */
utils::argument_parser prepare_parser()
{
try {
int delimiter_pos = str.find('x');
out.first = std::stoi(str.substr(0, delimiter_pos));
out.second = std::stoi(str.substr(delimiter_pos+1, std::string::npos));
} catch (...) {
log.error("Error parsing \"{}\" as resolution, expected format is \"WIDTHxHEIGHT\"", str);
return false;
}
return true;
utils::argument_parser parser("vgraph");

parser.add_argument(key::help, utils::argument().flag().option("-h").option("--help") .description("Print this help message"));
parser.add_argument(key::debug, utils::argument().flag().option("-d").option("--debug") .description("Enable debug logs"));
parser.add_argument(key::gpu, utils::argument().flag().option("-g").option("--gpu") .description("Use Nvidia GPU for processing"));
parser.add_argument(key::timecode, utils::argument().flag().option("-c").option("--timecode") .description("Draw a timecode on each frame"));
parser.add_argument(key::alignment, utils::argument().flag().option("-l").option("--alignment") .description(std::format("Enable alignment mode ({}s clip unless overriden by -T)", consts::defult_alignment_clip_time)));
parser.add_argument(key::telemetry, utils::argument() .option("-t").option("--telemetry") .description("Telemetry file path"));
parser.add_argument(key::layout, utils::argument() .option("-a").option("--layout") .description("Layout file path"));
parser.add_argument(key::input, utils::argument() .option("-i").option("--input") .description("Input video file path"));
parser.add_argument(key::output, utils::argument() .option("-o").option("--output") .description("Output video file path"));
parser.add_argument(key::offset, utils::argument() .option("-s").option("--offset") .description("Telemetry offset in seconds (video time at which track starts)"));
parser.add_argument(key::resolution, utils::argument() .option("-r").option("--resolution").description("Output video resolution, format: WIDTHxHEIGHT"));
parser.add_argument(key::bitrate, utils::argument() .option("-b").option("--bitrate") .description("Output video bitrate, in kbit/s"));
parser.add_argument(key::clip_time, utils::argument() .option("-T").option("--clip-time") .description("Generate clip limited to provided time in seconds (e.g. in combination with alignment mode)"));

return parser;
}

arguments read_args(const utils::argument_parser& parser, utils::logging::logger& log)
Expand All @@ -91,23 +92,25 @@ arguments read_args(const utils::argument_parser& parser, utils::logging::logger

a.debug = parser.get<bool>(key::debug);
a.gpu = parser.get<bool>(key::gpu);

valid = read_optional_value<std::string>(parser, key::telemetry, log, a.telemetry) && valid;
valid = read_optional_value<std::string>(parser, key::layout, log, a.layout) && valid;
valid = read_mandatory_value<std::string>(parser, key::input, log, a.input) && valid;
valid = read_mandatory_value<std::string>(parser, key::output, log, a.output) && valid;
valid = read_optional_value<double>(parser, key::offset, log, a.offset) && valid;

a.timecode = parser.get<bool>(key::timecode);

std::string res_str;
if (read_mandatory_value<std::string>(parser, key::resolution, log, res_str)) {
valid = parse_resolution(res_str, log, a.resolution) && valid;
} else {
valid = false;
a.alignment_mode = parser.get<bool>(key::alignment);

valid = read_value<std::string>(parser, key::telemetry, log, a.telemetry) && valid;
valid = read_value<std::string>(parser, key::layout, log, a.layout) && valid;
valid = read_value<std::string>(parser, key::input, log, a.input) && valid;
valid = read_value<std::string>(parser, key::output, log, a.output) && valid;
valid = read_value<double>(parser, key::offset, log, a.offset) && valid;
valid = read_value<int>(parser, key::bitrate, log, a.bitrate) && valid;
valid = read_value<double>(parser, key::clip_time, log, a.clip_time) && valid;

std::optional<std::string> res_str;
if (read_value<std::string>(parser, key::resolution, log, res_str) && res_str) {
valid = parse_resolution(*res_str, log, a.resolution) && valid;
}

valid = read_mandatory_value<int>(parser, key::bitrate, log, a.bitrate) && valid;
if (a.alignment_mode && !a.clip_time) {
a.clip_time = consts::defult_alignment_clip_time;
}

if (!valid) {
exit(1);
Expand All @@ -116,32 +119,65 @@ arguments read_args(const utils::argument_parser& parser, utils::logging::logger
return std::move(a);
}

arguments arguments::parse(int argc, char* argv[])
void assert_arguments_valid(const arguments& args, utils::logging::logger& log)
{
utils::logging::logger log{"arguments::parse()"};
bool valid = true;

try {
auto parser = prepare_parser();
if (!args.input) {
log.error("Missing mandatory argument: input");
valid = false;
}
if (!args.output) {
log.error("Missing mandatory argument: output");
valid = false;
}
if (!args.resolution) {
log.error("Missing mandatory argument: resolution");
valid = false;
}
if (!args.bitrate) {
log.error("Missing mandatory argument: bitrate");
valid = false;
}
if (args.alignment_mode && args.offset) {
log.error("Offset not allowed to be set when in alignment mode");
valid = false;
}

parser.parse(argc, argv);
if (parser.get<bool>(key::help)) {
parser.print_help();
exit(0); //ok case
}
if (!valid) {
exit(1);
}
}

return std::move(read_args(parser, log));
template <typename T>
bool read_value(const utils::argument_parser& parser, const std::string& name, utils::logging::logger& log, std::optional<T>& value_out)
{
if (!parser.has(name)) {
return true;
}

} catch (utils::argument_exception e) {
log.error(e.what());
exit(1);
} catch (std::exception e) {
log.error("Unexpected error: {}", e.what());
exit(1);
if (parser.count(name) > 1) {
log.error("Expected 1 value for {} got {}", name, parser.count(name));
return false;
}

//shall never reach
log.error("Execution error");
exit(2);
value_out = parser.get<T>(name);
return true;
}

bool parse_resolution(const std::string& str, utils::logging::logger& log, std::optional<std::pair<int, int>>& out)
{
try {
int delimiter_pos = str.find('x');
out = std::make_pair(std::stoi(str.substr(0, delimiter_pos)), std::stoi(str.substr(delimiter_pos+1, std::string::npos)));
log.debug("Parsed resolution width={} height={} ", out->first, out->second);
} catch (...) {
log.error("Error parsing \"{}\" as resolution, expected format is \"WIDTHxHEIGHT\"", str);
return false;
}
return true;
}



}
13 changes: 8 additions & 5 deletions src/arguments.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ namespace vgraph {
struct arguments {
bool debug = false;
bool gpu = false;
bool timecode = false;
bool alignment_mode = false;

std::optional<std::string> telemetry = std::nullopt;
std::optional<std::string> layout = std::nullopt;
std::string input = "";
std::string output = "";
std::optional<std::string> input = std::nullopt;
std::optional<std::string> output = std::nullopt;

std::optional<double> offset = std::nullopt;
bool timecode = false;

std::pair<int, int> resolution = {3840,2160};
int bitrate = {80*1024};
std::optional<std::pair<int, int>> resolution = std::nullopt;
std::optional<int> bitrate = std::nullopt;

std::optional<double> clip_time = std::nullopt;

static arguments parse(int argc, char* argv[]);
};
Expand Down
27 changes: 22 additions & 5 deletions src/manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@

#include "video/generator.h"
#include "video/overlay/layout.h"
#include "video/overlay/layout_parser.h"
#include "video/overlay/overlay.h"
#include "video/overlay/widget/timecode_widget.h"

#include <iostream>
#include <chrono>
#include <memory>

namespace vgraph {

Expand Down Expand Up @@ -44,9 +47,11 @@ void manager::run()

auto t2 = std::chrono::high_resolution_clock::now();

video::overlay::layout_parser layout_parser;
std::shared_ptr<video::overlay::layout> lay = nullptr;
if (args.layout) {
if (args.alignment_mode) {
lay = video::overlay::generate_alignment_layout(tele, *args.resolution);
} else if (args.layout) {
video::overlay::layout_parser layout_parser;
lay = layout_parser.parse(*args.layout);
if (!lay) {
log.error("Unable to load layout from defined path - exitting...");
Expand All @@ -56,12 +61,24 @@ void manager::run()
log.warning("NO LAYOUT FILE PROVIDED - NO DATA-DRIVEN OVERLAY WILL BE GENERATED");
}

video::overlay::overlay overlay(lay, tele, args.resolution, args.timecode);
overlay.precache();
if (args.timecode) {
if (!lay) {
lay = std::make_shared<video::overlay::layout>();
}
lay->push_back(std::make_shared<video::overlay::timecode_widget>(args.resolution->first / 2));
}

std::shared_ptr<video::overlay::overlay> overlay;
if (tele != nullptr && lay != nullptr) {
overlay = std::make_shared<video::overlay::overlay>(lay, tele, *args.resolution);
overlay->precache();
}

auto t3 = std::chrono::high_resolution_clock::now();

video::generator gen(args.input, args.output, overlay, args.gpu, args.resolution, args.bitrate, args.debug);
video::generator gen(*args.input, *args.output, overlay,
args.gpu, *args.resolution, *args.bitrate,
args.debug, args.clip_time);
gen.generate();

auto t4 = std::chrono::high_resolution_clock::now();
Expand Down
2 changes: 2 additions & 0 deletions src/telemetry/datapoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ struct datapoint {
using datapoint_ptr = std::shared_ptr<datapoint>;
using datapoint_seq = std::vector<datapoint_ptr>;

using timedatapoint = std::pair<double, datapoint_ptr>;

} // namespace telemetry
} // namespace vgraph

Expand Down
Loading

0 comments on commit 70190b3

Please sign in to comment.