diff --git a/MSVC/m4acut.props b/MSVC/m4acut.props index 9eadc62..806a199 100644 --- a/MSVC/m4acut.props +++ b/MSVC/m4acut.props @@ -3,7 +3,7 @@ - liblsmash.lib + lsmash.lib @@ -19,4 +19,4 @@ $(LSMASH_LIBDIR) - \ No newline at end of file + diff --git a/src/M4ATrimmer.h b/src/M4ATrimmer.h index d5df926..0d56052 100644 --- a/src/M4ATrimmer.h +++ b/src/M4ATrimmer.h @@ -12,6 +12,7 @@ #include #include #include +#include #include extern "C" { #define LSMASH_DEMUXER_ENABLED @@ -44,6 +45,52 @@ class StringPool { } }; +class MP4Edits { + typedef std::pair entry_t; + std::vector m_edits; +public: + void add_entry(int64_t offset, int64_t duration) + { + m_edits.push_back(std::make_pair(offset, duration)); + } + size_t count() const { return m_edits.size(); } + uint64_t total_duration() const + { + return std::accumulate(m_edits.begin(), m_edits.end(), 0ULL, + [](uint64_t n, const entry_t &e) -> uint64_t { + return n + e.second; + }); + } + int64_t offset(unsigned edit_index) const + { + return m_edits[edit_index].first; + } + int64_t duration(unsigned edit_index) const + { + return m_edits[edit_index].second; + } + unsigned edit_for_position(int64_t position, int64_t *offset=0) const + { + int64_t acc = 0; + int64_t off = 0; + size_t i = 0; + for (; i < m_edits.size(); ++i) { + off = position - acc; + acc += m_edits[i].second; + if (position < acc) + break; + } + if (offset) *offset = off; + return i == m_edits.size() ? i - 1 : i; + } + int64_t media_offset_for_position(int64_t position) const + { + int64_t off; + unsigned edit = edit_for_position(position, &off); + return offset(edit) + off; + } +}; + class M4ATrimmer { struct FileParameters: lsmash_file_parameters_t { FileParameters(const std::string &filename, int open_mode) @@ -65,18 +112,9 @@ class M4ATrimmer { * upsampled timescale * 0: oterwise */ - uint32_t media_offset; /* - * start offset in the media - * in media timescale - * downsampled if dual-rate SBR - */ - uint64_t media_valid_duration; /* - * valid duration in the media - * in media timescale - * downsampled if dual-rate SBR - */ + MP4Edits edits; - Track(): upsampled(0), media_offset(0), media_valid_duration(0) + Track(): upsampled(0) { memset(&track_params, 0, sizeof track_params); memset(&media_params, 0, sizeof media_params); @@ -92,9 +130,7 @@ class M4ATrimmer { } uint64_t duration() const { - if (media_valid_duration) - return media_valid_duration; - return (media_params.duration >> upsampled) - media_offset; + return edits.total_duration(); } }; struct Input { @@ -161,6 +197,11 @@ class M4ATrimmer { lsmash_cleanup_itunes_metadata(&item); } fetch_chapters(); + if (!m_input.track.edits.count()) { + int64_t duration = + m_input.track.media_params.duration >> m_input.track.upsampled; + m_input.track.edits.add_entry(0, duration); + } } void open_output(const std::string &filename) { @@ -197,40 +238,60 @@ class M4ATrimmer { void select_cut_point(const TimeSpec &startspec, const TimeSpec &endspec) { - int32_t off = m_input.track.media_offset; - int64_t start = startspec.is_samples ? - startspec.value.samples + startspec.value.samples >> m_input.track.upsampled : startspec.value.seconds * m_input.track.timescale() + .5; int64_t end = endspec.is_samples ? - endspec.value.samples + endspec.value.samples >> m_input.track.upsampled : endspec.value.seconds * m_input.track.timescale() + .5; if (start > m_input.track.duration()) throw std::runtime_error("the start position for trimming exceeds " "the length of input"); - m_cut_start = std::max(static_cast(0), - start + off - 1024) / 1024; - m_output.track.media_offset = start + off - m_cut_start * 1024; - if (end <= 0) end = m_input.track.duration(); if (end <= start) throw std::runtime_error("the end position of trimming is before " "the start position"); - m_cut_end = (end + off + 1023) / 1024; - /* for the sake of SBR, we extend padding if len(padding) < 481 */ - if (m_cut_end * 1024 - (end + off) < 481) - ++m_cut_end; + + const MP4Edits &edits = m_input.track.edits; + int64_t media_start = edits.media_offset_for_position(start); + int64_t media_end = edits.media_offset_for_position(end - 1) + 1; + m_cut_start = m_current_au = + std::max(static_cast(0), media_start - 1024) / 1024; + m_cut_end = (media_end + 1023) / 1024 + 1; uint64_t num_au = m_input.track.num_access_units(); if (m_cut_end > num_au) m_cut_end = num_au; - int64_t duration_plus_padding = - m_cut_end * 1024 - m_output.track.media_offset; - m_output.track.media_valid_duration = - std::min(end - start, duration_plus_padding); - - m_current_au = m_cut_start; + unsigned i = edits.edit_for_position(start); + unsigned j = edits.edit_for_position(end - 1); + int64_t start_edit_off = edits.offset(i); + int64_t start_edit_end = edits.offset(i) + edits.duration(i); + + int64_t new_start_edit_off = media_start - m_cut_start * 1024; + int64_t new_start_edit_duration = + std::min(media_end, start_edit_end) - media_start; + int64_t edit_shift = new_start_edit_off - start_edit_off + + new_start_edit_duration - edits.duration(i); + + m_output.track.edits.add_entry(new_start_edit_off, + new_start_edit_duration); + for (unsigned k = i + 1; k < j; ++k) + m_output.track.edits.add_entry(edits.offset(k) + edit_shift, + edits.duration(k)); + if (i < j) + m_output.track.edits.add_entry(edits.offset(j) + edit_shift, + media_end - edits.offset(j)); + + unsigned count = m_output.track.edits.count(); + for (unsigned i = 0; i < count; ++i) { + lsmash_edit_t edit = { 0 }; + edit.duration = m_output.track.edits.duration(i); + edit.start_time = m_output.track.edits.offset(i); + edit.rate = ISOM_EDIT_MODE_NORMAL; + lsmash_create_explicit_timeline_map(m_output.movie.get(), + m_output.track.id(), edit); + } } void select_chapter(unsigned nth) { @@ -256,12 +317,8 @@ class M4ATrimmer { lsmash_get_sample_from_media_timeline(m_input.movie.get(), m_input.track.id(), m_current_au + 1); - if (!sample) { - /* treat as EOF, update duration */ - m_output.track.media_valid_duration = - m_current_au * 1024 - m_output.track.media_offset; + if (!sample) return false; - } sample->dts = sample->cts = (m_current_au - m_cut_start) * 1024; /* * XXX: leaks a sample when lsmash_append_sample() fails. @@ -276,7 +333,8 @@ class M4ATrimmer { { DieIF(lsmash_flush_pooled_samples(m_output.movie.get(), m_output.track.id(), 1024)); - write_iTunSMPB(); + if (m_output.track.edits.count() == 1) + write_iTunSMPB(); lsmash_adhoc_remux_t param; param.func = cb; param.buffer_size = 4 * 1024 * 1024; @@ -342,15 +400,18 @@ class M4ATrimmer { if (au_duration == 2048) t->upsampled = 1; - if (lsmash_count_explicit_timeline_map(mov, track_id) == 1) { - lsmash_edit_t edit; - DieIF(lsmash_get_explicit_timeline_map(mov, track_id, 1, &edit)); - t->media_offset = edit.start_time >> t->upsampled; - if (edit.duration > 0 && edit.duration < t->track_params.duration) { + if (m_input.movie_params.timescale >= t->media_params.timescale) { + uint32_t nedits = + lsmash_count_explicit_timeline_map(mov, track_id); + for (uint32_t i = 1; i <= nedits; ++i) { + lsmash_edit_t edit; + DieIF(lsmash_get_explicit_timeline_map(mov, track_id, + i, &edit)); double duration = static_cast(edit.duration); duration /= m_input.movie_params.timescale; - duration *= t->timescale(); - t->media_valid_duration = static_cast(duration + 0.5); + duration *= t->media_params.timescale; + t->edits.add_entry(edit.start_time >> t->upsampled, + int64_t(duration + .5) >> t->upsampled); } } } @@ -392,6 +453,9 @@ class M4ATrimmer { } else return true; + if (m_input.track.edits.count()) + return true; + std::stringstream ss(std::string(s, len)); uint32_t junk, priming, padding; uint64_t duration; @@ -400,9 +464,9 @@ class M4ATrimmer { >> std::hex >> padding >> std::hex >> duration) { - unsigned shift = m_input.track.upsampled; - m_input.track.media_offset = priming >> shift; - m_input.track.media_valid_duration = duration >> shift; + priming >>= m_input.track.upsampled; + duration >>= m_input.track.upsampled; + m_input.track.edits.add_entry(priming, duration); } return true; } @@ -481,15 +545,19 @@ class M4ATrimmer { auto p = std::make_pair(ss - start_time, std::string(title)); m_input.chapters.push_back(p); } - if (start_time && !m_input.track.media_offset) { - m_input.track.media_offset = - m_input.track.timescale() * start_time + .5; + if (start_time && !m_input.track.edits.count()) { + int64_t off = m_input.track.timescale() * start_time + .5; + int64_t duration = + m_input.track.media_params.duration >> m_input.track.upsampled; + duration -= off; + m_input.track.edits.add_entry(off, duration); } } void add_audio_track() { lsmash_root_t *mov = m_output.movie.get(); - m_output.track = m_input.track; + m_output.track.track_params = m_input.track.track_params; + m_output.track.media_params = m_input.track.media_params; uint32_t trakid; DieIF(!(trakid = @@ -535,12 +603,15 @@ class M4ATrimmer { char buf[256]; uint64_t total_duration = (m_current_au - m_cut_start) * 1024; - uint32_t padding = - total_duration - m_output.track.media_offset - - m_output.track.media_valid_duration; - std::sprintf(buf, fmt, m_output.track.media_offset, padding, - int(m_output.track.media_valid_duration >> 32), - int(m_output.track.media_valid_duration & 0xffffffff)); + unsigned offset = m_output.track.edits.offset(0); + uint64_t duration = m_output.track.edits.duration(0); + int32_t padding = total_duration - offset - duration; + if (padding < 0) { + padding = 0; + duration += padding; + } + std::sprintf(buf, fmt, offset, padding, int(duration >> 32), + int(duration & 0xffffffff)); lsmash_itunes_metadata_t tag; memset(&tag, 0, sizeof tag); diff --git a/src/main.cpp b/src/main.cpp index c5a0e14..a703500 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -81,8 +81,6 @@ void usage() " 588s : 588 samples\n" " 1:23.586 : 1m 23.586s\n" " 15 : 15s\n" -" Note that you have to count in half the sample rate\n" -" in case of dual-rate HE-AAC (1 means 22050s).\n" " -e, --end <[[hh:]mm:]ss[.ss..]|ns>\n" " Specify cut end point (exclusive).\n" " When not given, end of input is assumed.\n"