diff --git a/src/common/split_arg_parsing.cpp b/src/common/split_arg_parsing.cpp new file mode 100644 index 000000000..1540482da --- /dev/null +++ b/src/common/split_arg_parsing.cpp @@ -0,0 +1,102 @@ +/** \brief command line parsing + + mkvmerge -- utility for splicing together matroska files + from component media subtypes + + Distributed under the GPL + see the file COPYING for details + or visit http://www.gnu.org/copyleft/gpl.html + + \author Written by Moritz Bunkus . +*/ + +#include "common/common_pch.h" + +#include "common/split_arg_parsing.h" +#include "common/strings/editing.h" +#include "common/strings/parsing.h" + +namespace mtx { namespace args { + +std::vector +parse_split_parts(const std::string &arg, + bool frames_fields) { + std::string s = arg; + + if (balg::istarts_with(s, "parts:")) + s.erase(0, 6); + + else if (balg::istarts_with(s, "parts-frames:")) + s.erase(0, 13); + + if (s.empty()) + throw format_x{boost::format(Y("Missing start/end specifications for '--split' in '--split %1%'.\n")) % arg}; + + std::vector > requested_split_points; + for (auto const &part_spec : split(s, ",")) { + auto pair = split(part_spec, "-"); + if (pair.size() != 2) + throw format_x{boost::format(Y("Invalid start/end specification for '--split' in '--split %1%' (curent part: %2%).\n")) % arg % part_spec}; + + bool create_new_file = true; + if (pair[0].substr(0, 1) == "+") { + if (!requested_split_points.empty()) + create_new_file = false; + pair[0].erase(0, 1); + } + + int64_t start; + if (pair[0].empty()) + start = requested_split_points.empty() ? 0 : std::get<1>(requested_split_points.back()); + + else if (!frames_fields && !parse_timecode(pair[0], start)) + throw format_x{boost::format(Y("Invalid start time for '--split' in '--split %1%' (current part: %2%). Additional error message: %3%.\n")) % arg % part_spec % timecode_parser_error}; + + else if (frames_fields && (!parse_number(pair[0], start) || (0 > start))) + throw format_x{boost::format(Y("Invalid start frame/field number for '--split' in '--split %1%' (current part: %2%).\n")) % arg % part_spec}; + + int64_t end; + if (pair[1].empty()) + end = std::numeric_limits::max(); + + else if (!frames_fields && !parse_timecode(pair[1], end)) + throw format_x{boost::format(Y("Invalid end time for '--split' in '--split %1%' (current part: %2%). Additional error message: %3%.\n")) % arg % part_spec % timecode_parser_error}; + + else if (frames_fields && (!parse_number(pair[1], end) || (0 > end))) + throw format_x{boost::format(Y("Invalid end frame/field number for '--split' in '--split %1%' (current part: %2%).\n")) % arg % part_spec}; + + if (end <= start) { + if (frames_fields) + throw format_x{boost::format(Y("Invalid end frame/field number for '--split' in '--split %1%' (current part: %2%). The end number must be bigger than the start number.\n")) % arg % part_spec}; + else + throw format_x{boost::format(Y("Invalid end time for '--split' in '--split %1%' (current part: %2%). The end time must be bigger than the start time.\n")) % arg % part_spec}; + } + + if (!requested_split_points.empty() && (start < std::get<1>(requested_split_points.back()))) { + if (frames_fields) + throw format_x{boost::format(Y("Invalid start frame/field number for '--split' in '--split %1%' (current part: %2%). The start number must be bigger than or equal to the previous part's end number.\n")) % arg % part_spec}; + else + throw format_x{boost::format(Y("Invalid start time for '--split' in '--split %1%' (current part: %2%). The start time must be bigger than or equal to the previous part's end time.\n")) % arg % part_spec}; + } + + requested_split_points.push_back(std::make_tuple(start, end, create_new_file)); + } + + std::vector split_points; + auto sp_type = frames_fields ? split_point_c::parts_frame_field : split_point_c::parts; + int64_t previous_end = 0; + + for (auto &split_point : requested_split_points) { + if (previous_end < std::get<0>(split_point)) + split_points.push_back(split_point_c{ previous_end, sp_type, true, true, std::get<2>(split_point) }); + split_points.push_back(split_point_c{ std::get<0>(split_point), sp_type, true, false, std::get<2>(split_point) }); + previous_end = std::get<1>(split_point); + } + + if (std::get<1>(requested_split_points.back()) < std::numeric_limits::max()) + split_points.push_back(split_point_c{ std::get<1>(requested_split_points.back()), sp_type, true, true }); + + return split_points; +} + +}} diff --git a/src/common/split_arg_parsing.h b/src/common/split_arg_parsing.h new file mode 100644 index 000000000..fe8c387e5 --- /dev/null +++ b/src/common/split_arg_parsing.h @@ -0,0 +1,39 @@ +/** \brief command line parsing + + mkvmerge -- utility for splicing together matroska files + from component media subtypes + + Distributed under the GPL + see the file COPYING for details + or visit http://www.gnu.org/copyleft/gpl.html + + \author Written by Moritz Bunkus . +*/ + +#ifndef MTX_COMMON_SPLIT_ARG_PARSING_H +#define MTX_COMMON_SPLIT_ARG_PARSING_H + +#include "common/common_pch.h" + +#include "common/split_point.h" + +namespace mtx { namespace args { + +class format_x: public exception { +protected: + std::string m_message; +public: + explicit format_x(std::string const &message) : m_message(message) { } + explicit format_x(boost::format const &message): m_message(message.str()) { } + virtual ~format_x() throw() { } + + virtual char const *what() const throw() { + return m_message.c_str(); + } +}; + +std::vector parse_split_parts(const std::string &arg, bool frames_fields); + +}} + +#endif // MTX_COMMON_SPLIT_ARG_PARSING_H diff --git a/src/common/split_point.cpp b/src/common/split_point.cpp index 744764a5e..ecea162a0 100644 --- a/src/common/split_point.cpp +++ b/src/common/split_point.cpp @@ -17,7 +17,7 @@ #include "common/strings/formatting.h" std::string -split_point_t::str() +split_point_c::str() const { return (boost::format("<%1% %2% once:%3% discard:%4% create_file:%5%>") % format_timecode(m_point) diff --git a/src/common/split_point.h b/src/common/split_point.h index 7e0af81b4..9a0e21e45 100644 --- a/src/common/split_point.h +++ b/src/common/split_point.h @@ -16,7 +16,7 @@ #include "common/common_pch.h" -class split_point_t { +class split_point_c { public: enum type_e { duration, @@ -33,7 +33,7 @@ public: bool m_use_once, m_discard, m_create_new_file; public: - split_point_t(int64_t point, + split_point_c(int64_t point, type_e type, bool use_once, bool discard = false, @@ -47,7 +47,7 @@ public: } bool - operator <(split_point_t const &rhs) + operator <(split_point_c const &rhs) const { return m_point < rhs.m_point; } diff --git a/src/merge/cluster_helper.cpp b/src/merge/cluster_helper.cpp index 773482faa..b0794a7d0 100644 --- a/src/merge/cluster_helper.cpp +++ b/src/merge/cluster_helper.cpp @@ -131,7 +131,7 @@ cluster_helper_c::split_if_necessary(packet_cptr &packet) { bool split_now = false; // Maybe we want to start a new file now. - if (split_point_t::size == m_current_split_point->m_type) { + if (split_point_c::size == m_current_split_point->m_type) { int64_t additional_size = 0; if (!m_packets.empty()) @@ -146,18 +146,18 @@ cluster_helper_c::split_if_necessary(packet_cptr &packet) { if ((m_header_overhead + additional_size + m_bytes_in_file) >= m_current_split_point->m_point) split_now = true; - } else if ( (split_point_t::duration == m_current_split_point->m_type) + } else if ( (split_point_c::duration == m_current_split_point->m_type) && (0 <= m_first_timecode_in_file) && (packet->assigned_timecode - m_first_timecode_in_file) >= m_current_split_point->m_point) split_now = true; - else if ( ( (split_point_t::timecode == m_current_split_point->m_type) - || (split_point_t::parts == m_current_split_point->m_type)) + else if ( ( (split_point_c::timecode == m_current_split_point->m_type) + || (split_point_c::parts == m_current_split_point->m_type)) && (packet->assigned_timecode >= m_current_split_point->m_point)) split_now = true; - else if ( ( (split_point_t::frame_field == m_current_split_point->m_type) - || (split_point_t::parts_frame_field == m_current_split_point->m_type)) + else if ( ( (split_point_c::frame_field == m_current_split_point->m_type) + || (split_point_c::parts_frame_field == m_current_split_point->m_type)) && (m_frame_field_number >= m_current_split_point->m_point)) split_now = true; @@ -183,8 +183,8 @@ cluster_helper_c::split(packet_cptr &packet) { if (m_current_split_point->m_use_once) { if ( m_current_split_point->m_discard - && ( (split_point_t::parts == m_current_split_point->m_type) - || (split_point_t::parts_frame_field == m_current_split_point->m_type)) + && ( (split_point_c::parts == m_current_split_point->m_type) + || (split_point_c::parts_frame_field == m_current_split_point->m_type)) && (m_split_points.end() == (m_current_split_point + 1))) { mxdebug_if(m_debug_splitting, boost::format("Splitting: Last part in 'parts:' splitting mode finished\n")); m_splitting_and_processed_fully = true; @@ -658,7 +658,7 @@ cluster_helper_c::handle_discarded_duration(bool create_new_file, } void -cluster_helper_c::add_split_point(const split_point_t &split_point) { +cluster_helper_c::add_split_point(const split_point_c &split_point) { m_split_points.push_back(split_point); m_current_split_point = m_split_points.begin(); m_discarding = m_current_split_point->m_discard; @@ -673,8 +673,8 @@ cluster_helper_c::split_mode_produces_many_files() if (!splitting()) return false; - if ( (split_point_t::parts != m_split_points.front().m_type) - && (split_point_t::parts_frame_field != m_split_points.front().m_type)) + if ( (split_point_c::parts != m_split_points.front().m_type) + && (split_point_c::parts_frame_field != m_split_points.front().m_type)) return true; bool first = true; @@ -698,7 +698,7 @@ cluster_helper_c::dump_split_points() const { mxdebug_if(m_debug_splitting, boost::format("Split points:%1%\n") - % boost::accumulate(m_split_points, std::string(""), [](std::string const &accu, split_point_t const &point) { return accu + " " + point.str(); })); + % boost::accumulate(m_split_points, std::string(""), [](std::string const &accu, split_point_c const &point) { return accu + " " + point.str(); })); } cluster_helper_c *g_cluster_helper = nullptr; diff --git a/src/merge/cluster_helper.h b/src/merge/cluster_helper.h index dfa9fd60a..749ee6797 100644 --- a/src/merge/cluster_helper.h +++ b/src/merge/cluster_helper.h @@ -57,8 +57,8 @@ private: bool m_first_video_keyframe_seen; mm_io_c *m_out; - std::vector m_split_points; - std::vector::iterator m_current_split_point; + std::vector m_split_points; + std::vector::iterator m_current_split_point; std::map m_id_timecode_duration_map; size_t m_num_cue_points_postprocessed; @@ -92,7 +92,7 @@ public: int64_t get_discarded_duration() const; void handle_discarded_duration(bool create_new_file, bool previously_discarding); - void add_split_point(const split_point_t &split_point); + void add_split_point(split_point_c const &split_point); void dump_split_points() const; bool splitting() const { return !m_split_points.empty(); diff --git a/src/merge/mkvmerge.cpp b/src/merge/mkvmerge.cpp index 7076fb5ce..26cee6c79 100644 --- a/src/merge/mkvmerge.cpp +++ b/src/merge/mkvmerge.cpp @@ -52,6 +52,7 @@ #include "common/iso639.h" #include "common/mm_io.h" #include "common/segmentinfo.h" +#include "common/split_arg_parsing.h" #include "common/strings/formatting.h" #include "common/strings/parsing.h" #include "common/unique_numbers.h" @@ -793,7 +794,7 @@ parse_arg_split_duration(const std::string &arg) { if (!parse_timecode(s, split_after)) mxerror(boost::format(Y("Invalid time for '--split' in '--split %1%'. Additional error message: %2%\n")) % arg % timecode_parser_error); - g_cluster_helper->add_split_point(split_point_t(split_after, split_point_t::duration, false)); + g_cluster_helper->add_split_point(split_point_c(split_after, split_point_c::duration, false)); } /** \brief Parse the timecode format to \c --split @@ -813,7 +814,7 @@ parse_arg_split_timecodes(const std::string &arg) { int64_t split_after; if (!parse_timecode(timecode, split_after)) mxerror(boost::format(Y("Invalid time for '--split' in '--split %1%'. Additional error message: %2%.\n")) % arg % timecode_parser_error); - g_cluster_helper->add_split_point(split_point_t(split_after, split_point_t::timecode, true)); + g_cluster_helper->add_split_point(split_point_c(split_after, split_point_c::timecode, true)); } } @@ -834,7 +835,7 @@ parse_arg_split_frames(std::string const &arg) { uint64_t split_after = 0; if (!parse_number(frame, split_after) || (0 == split_after)) mxerror(boost::format(Y("Invalid frame for '--split' in '--split %1%'.\n")) % arg); - g_cluster_helper->add_split_point(split_point_t(split_after, split_point_t::frame_field, true)); + g_cluster_helper->add_split_point(split_point_c(split_after, split_point_c::frame_field, true)); } } @@ -864,7 +865,7 @@ parse_arg_split_chapters(std::string const &arg) { if (!g_kax_chapters) mxerror(boost::format(Y("No chapters in source files or chapter files found to split by.\n"))); - std::vector new_split_points; + std::vector new_split_points; auto current_number = 0u; for (auto element : *g_kax_chapters) { auto edition = dynamic_cast(element); @@ -883,7 +884,7 @@ parse_arg_split_chapters(std::string const &arg) { int64_t split_after = FindChildValue(atom, 0); if (split_after) - new_split_points.push_back(split_point_t{split_after, split_point_t::timecode, true}); + new_split_points.push_back(split_point_c{split_after, split_point_c::timecode, true}); } } @@ -905,79 +906,14 @@ parse_arg_split_chapters(std::string const &arg) { static void parse_arg_split_parts(const std::string &arg, bool frames_fields) { - std::string s = arg; + try { + auto split_points = mtx::args::parse_split_parts(arg, frames_fields); + for (auto &point : split_points) + g_cluster_helper->add_split_point(point); - if (balg::istarts_with(s, "parts:")) - s.erase(0, 6); - - else if (balg::istarts_with(s, "parts-frames:")) - s.erase(0, 13); - - if (s.empty()) - mxerror(boost::format(Y("Missing start/end specifications for '--split' in '--split %1%'.\n")) % arg); - - std::vector > split_points; - for (auto const &part_spec : split(s, ",")) { - auto pair = split(part_spec, "-"); - if (pair.size() != 2) - mxerror(boost::format(Y("Invalid start/end specification for '--split' in '--split %1%' (curent part: %2%).\n")) % arg % part_spec); - - bool create_new_file = true; - if (pair[0].substr(0, 1) == "+") { - if (!split_points.empty()) - create_new_file = false; - pair[0].erase(0, 1); - } - - int64_t start; - if (pair[0].empty()) - start = split_points.empty() ? 0 : std::get<1>(split_points.back()); - - else if (!frames_fields && !parse_timecode(pair[0], start)) - mxerror(boost::format(Y("Invalid start time for '--split' in '--split %1%' (current part: %2%). Additional error message: %3%.\n")) % arg % part_spec % timecode_parser_error); - - else if (frames_fields && (!parse_number(pair[0], start) || (0 > start))) - mxerror(boost::format(Y("Invalid start frame/field number for '--split' in '--split %1%' (current part: %2%).\n")) % arg % part_spec); - - int64_t end; - if (pair[1].empty()) - end = std::numeric_limits::max(); - - else if (!frames_fields && !parse_timecode(pair[1], end)) - mxerror(boost::format(Y("Invalid end time for '--split' in '--split %1%' (current part: %2%). Additional error message: %3%.\n")) % arg % part_spec % timecode_parser_error); - - else if (frames_fields && (!parse_number(pair[1], end) || (0 > end))) - mxerror(boost::format(Y("Invalid end frame/field number for '--split' in '--split %1%' (current part: %2%).\n")) % arg % part_spec); - - if (end <= start) { - if (frames_fields) - mxerror(boost::format(Y("Invalid end frame/field number for '--split' in '--split %1%' (current part: %2%). The end number must be bigger than the start number.\n")) % arg % part_spec); - else - mxerror(boost::format(Y("Invalid end time for '--split' in '--split %1%' (current part: %2%). The end time must be bigger than the start time.\n")) % arg % part_spec); - } - - if (!split_points.empty() && (start < std::get<1>(split_points.back()))) { - if (frames_fields) - mxerror(boost::format(Y("Invalid start frame/field number for '--split' in '--split %1%' (current part: %2%). The start number must be bigger than or equal to the previous part's end number.\n")) % arg % part_spec); - else - mxerror(boost::format(Y("Invalid start time for '--split' in '--split %1%' (current part: %2%). The start time must be bigger than or equal to the previous part's end time.\n")) % arg % part_spec); - } - - split_points.push_back(std::make_tuple(start, end, create_new_file)); + } catch (mtx::args::format_x &ex) { + mxerror(ex.what()); } - - auto sp_type = frames_fields ? split_point_t::parts_frame_field : split_point_t::parts; - int64_t previous_end = 0; - - for (auto &split_point : split_points) { - if (previous_end < std::get<0>(split_point)) - g_cluster_helper->add_split_point(split_point_t{ previous_end, sp_type, true, true, std::get<2>(split_point) }); - g_cluster_helper->add_split_point(split_point_t{ std::get<0>(split_point), sp_type, true, false, std::get<2>(split_point) }); - previous_end = std::get<1>(split_point); - } - - if (std::get<1>(split_points.back()) < std::numeric_limits::max()) - g_cluster_helper->add_split_point(split_point_t{ std::get<1>(split_points.back()), sp_type, true, true }); } /** \brief Parse the size format to \c --split @@ -1015,7 +951,7 @@ parse_arg_split_size(const std::string &arg) { if (!parse_number(s, split_after)) mxerror(boost::format(err_msg) % arg); - g_cluster_helper->add_split_point(split_point_t(split_after * modifier, split_point_t::size, false)); + g_cluster_helper->add_split_point(split_point_c(split_after * modifier, split_point_c::size, false)); } /** \brief Parse the \c --split argument