feat(legacy): visual cue point editor (#2947)

A visual cue point editor in the track editor view. This view displays the track as a waveform and allows you to set where the in- and out-cue points are set. These cue points determine the start and end points of the track.

---------

Co-authored-by: Thomas Göttgens <tgoettgens@mail.com>
Co-authored-by: Kyle Robbertze <paddatrapper@users.noreply.github.com>
This commit is contained in:
Thomas Göttgens 2024-04-21 11:13:43 +02:00 committed by GitHub
parent 71b20ae3c9
commit da02e74f21
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 9420 additions and 43 deletions

View file

@ -26,7 +26,7 @@
"css/media_library.css": "e1982d1f673543f7730898fb49450f8b",
"css/player-form.css": "e08a4545715fc56b75c845b44a5b2a1c",
"css/player.css": "904bc7aede4d5f0372468528d88094f1",
"css/playlist_builder.css": "e92ef56ddffca440a7741934edbb7f7f",
"css/playlist_builder.css": "9e35f1b7a1e79a7a73e7e9666d5a711f",
"css/playouthistory.css": "983cc1bac566b18b745b6e0da9ef3c0c",
"css/plupload.queue.css": "0acfb6b54c18654452727d4abf297394",
"css/pro_dropdown_3.css": "9848a27dad960c2218751c1656e9206a",
@ -43,7 +43,7 @@
"css/show_analytics.css": "4393c521308277447afabe8791779bf1",
"css/showbuilder.css": "4421c01b5c2dfb03f8d06dd6023b4bd7",
"css/station_podcast.css": "88e9b38ead71eddc69ef50bfc8cb2d0d",
"css/styles.css": "6890a553402f44cefc7c6915f38aa657",
"css/styles.css": "29ce2292b2c007e86d192d0deeaf88ad",
"css/tipsy/jquery.tipsy.css": "b13517583583f83ed7d5fc067a0c9372",
"css/tracktypes.css": "94c94817a8505ff4dfcd090987859a7e",
"css/users.css": "94c94817a8505ff4dfcd090987859a7e",
@ -63,7 +63,7 @@
"js/airtime/library/plupload.js": "0f6be5b133650828b9ffc74e7852dc89",
"js/airtime/library/podcast.js": "4dedd84cb571cdba2401bfb8ba621e69",
"js/airtime/library/publish.js": "ab3a1452dd332cdb0773241a1c17b7e0",
"js/airtime/library/spl.js": "c4cbac0c237b548064685a2cb16d3fa2",
"js/airtime/library/spl.js": "5bddd886303ff15e8b78e79b30a9e56f",
"js/airtime/listenerstat/listenerstat.js": "a3733dae8f9549668125ec9852d356ed",
"js/airtime/listenerstat/showlistenerstat.js": "7cf0c375420f1c8471d304bc8758b2cd",
"js/airtime/login/login.js": "7278cf49618791d75bacce38dd1b1d46",
@ -180,5 +180,16 @@
"js/waveformplaylist/playout.js": "7dfc5fe760f3c6739e38499df7b61e47",
"js/waveformplaylist/time_scale.js": "74e0e17e1c8cd597449220c98de408ba",
"js/waveformplaylist/track.js": "5456e6081ffedf55a9e38571bc178781",
"js/waveformplaylist/track_render.js": "e371b582b23e4b618e039f096d2f0570"
"js/waveformplaylist/track_render.js": "e371b582b23e4b618e039f096d2f0570",
"js/wavesurfer/cursor.js": "8ed17a7437f3ec84972d15d0073249b2",
"js/wavesurfer/cursor.min.js": "831165862b629e615cf59112fa00d963",
"js/wavesurfer/libretime.js": "17133cacf09fc204a572b56c99d44278",
"js/wavesurfer/minimap.js": "c17dd315386006bb3cffdabb5f715c7b",
"js/wavesurfer/minimap.min.js": "90b2f2d1d1b4eb189d1a9c3c27dcb4f7",
"js/wavesurfer/regions.js": "aafe4f696d3da50c976d11e472fd56d1",
"js/wavesurfer/regions.min.js": "2ed2f8b5880beee568942000a6139e85",
"js/wavesurfer/timeline.js": "0bd70779070513c2a4f34237a0f9f573",
"js/wavesurfer/timeline.min.js": "90ea16b23cacebfad10cad42f94403d0",
"js/wavesurfer/wavesurfer.js": "9e2ced8a136449f4fd78911b0f01f6ed",
"js/wavesurfer/wavesurfer.min.js": "42ebd7fdd574dfe8cae587145751a1f2"
}

View file

@ -400,6 +400,12 @@ class LibraryController extends Zend_Controller_Action
$this->view->artist_name = $file->getPropelOrm()->getDbArtistName();
$this->view->filePath = $file->getPropelOrm()->getDbFilepath();
$this->view->artwork = $file->getPropelOrm()->getDbArtwork();
$this->view->replay_gain = $file->getPropelOrm()->getDbReplayGain();
$this->view->cuein = $file->getPropelOrm()->getDbCuein();
$this->view->cueout = $file->getPropelOrm()->getDbCueout();
$this->view->format = $file->getPropelOrm()->getDbFormat();
$this->view->bit_rate = $file->getPropelOrm()->getDbBitRate();
$this->view->sample_rate = $file->getPropelOrm()->getDbSampleRate();
$this->view->html = $this->view->render('library/edit-file-md.phtml');
}

View file

@ -217,6 +217,13 @@ class PageLayoutInitPlugin extends Zend_Controller_Plugin_Abstract
->appendFile(Assets::url('js/airtime/common/common.js'), 'text/javascript')
->appendFile(Assets::url('js/airtime/common/audioplaytest.js'), 'text/javascript');
// include wavesurfer.js for waveform display
$view->headScript()->appendFile(Assets::url('js/wavesurfer/wavesurfer.min.js'), 'text/javascript')
->appendFile(Assets::url('js/wavesurfer/timeline.min.js'), 'text/javascript')
->appendFile(Assets::url('js/wavesurfer/regions.min.js'), 'text/javascript')
->appendFile(Assets::url('js/wavesurfer/cursor.min.js'), 'text/javascript')
->appendFile(Assets::url('js/wavesurfer/libretime.js'), 'text/javascript');
$user = Application_Model_User::getCurrentUser();
if (!is_null($user)) {
$userType = $user->getType();

View file

@ -8,9 +8,11 @@ class Application_Form_EditAudioMD extends Zend_Form
{
// Set the method for the display form to POST
$this->setMethod('post');
$this->setAttrib('id', 'track_edit_' . $p_id);
$file_id = new Zend_Form_Element_Hidden('file_id');
$file_id->setValue($p_id);
$file_id->setDecorators(['ViewHelper']);
$file_id->addDecorator('HtmlTag', ['tag' => 'div', 'style' => 'display:none']);
$file_id->removeDecorator('Label');
$file_id->setAttrib('class', 'obj_id');
@ -188,6 +190,14 @@ class Application_Form_EditAudioMD extends Zend_Form
]);
$this->addElement($mood);
// Add replay gain field
$replay_gain = new Zend_Form_Element_Hidden('replay_gain');
$replay_gain->class = 'input_text replay_gain_' . $p_id;
$replay_gain->setLabel(_('Replay Gain:'))
->addDecorator('HtmlTag', ['tag' => 'div', 'style' => 'display:none'])
->removeDecorator('Label');
$this->addElement($replay_gain);
// Add bmp field
$bpm = new Zend_Form_Element_Text('bpm');
$bpm->class = 'input_text';
@ -242,7 +252,7 @@ class Application_Form_EditAudioMD extends Zend_Form
$validCuePattern = '/^(?:[0-9]{1,2}:)?(?:[0-9]{1,2}:)?[0-9]{1,6}(\.\d{1,6})?$/';
$cueIn = new Zend_Form_Element_Text('cuein');
$cueIn->class = 'input_text';
$cueIn->class = 'input_text cuein_' . $p_id;
$cueIn->setLabel('Cue In:');
$cueInValidator = Application_Form_Helper_ValidationTypes::overrideRegexValidator(
$validCuePattern,
@ -252,7 +262,7 @@ class Application_Form_EditAudioMD extends Zend_Form
$this->addElement($cueIn);
$cueOut = new Zend_Form_Element_Text('cueout');
$cueOut->class = 'input_text';
$cueOut->class = 'input_text cueout_' . $p_id;
$cueOut->setLabel('Cue Out:');
$cueOutValidator = Application_Form_Helper_ValidationTypes::overrideRegexValidator(
$validCuePattern,

View file

@ -1,39 +1,186 @@
<?php $get_artwork = FileDataHelper::getArtworkData($this->artwork, 256); ?>
<?php $get_artwork = FileDataHelper::getArtworkData($this->artwork, 256);
$baseUrl = Config::getBasePath();
$get_replay_gain = Application_Model_Preference::getReplayGainModifier();
if (!Application_Model_Preference::GetEnableReplayGain() ) {
$get_replay_gain = 0;
}
$analogMeter = true;
?>
<div class="ui-widget ui-widget-content block-shadow simple-formblock clearfix padded-strong edit-md-dialog">
<div class="track-edit-header" style="top:15px">
<?php if ($this->permissionDenied) { ?> <h3><?php echo _("You do not have permission to edit this track.") ?></h3> <?php } ?>
<?php
/*
if ($this->permissionDenied) {
echo(_("Viewing "));
} else {
echo(_("Editing "));
} */
?>
<div class="track-edit-right-wrapper">
<div class="track-edit-right">
<div class="inner_track_editor_title" style="width: 100%;">
<h2 style="line-height: 26px !important;"><span class="title_obj_name"><?php echo ($this->title); ?></span></h2>
<h3 style="line-height: 2px !important;"><span class=""><?php echo ($this->artist_name); ?></span></h3>
</div>
</div>
</div>
<div class="track-edit-left">
<div class="artwork-upload" data-id="<?php echo ($this->id); ?>">
<div class="artwork-edit">
<input type='file' class="artworkUpload artwork-uploaded-<?php echo ($this->id); ?>" id="artworkUpload-<?php echo ($this->id); ?>" data-id="<?php echo ($this->id); ?>" accept=".png, .jpg, .jpeg" />
<label for="artworkUpload-<?php echo ($this->id); ?>"></label>
</div>
<div id="artwork-preview" class="artwork-preview">
<div class="artwork-preview-<?php echo ($this->id); ?>" id="artworkPreview" style="background-image: url(<?php echo $get_artwork; ?>);">
</div>
</div>
</div>
<div>
<a href="#" class="delete-artwork" data-id="<?php echo ($this->id); ?>" style="font-size: 11px;">Remove</a>
<div class="track-edit-header" style="top:15px">
<?php if ($this->permissionDenied) { ?> <h3><?php echo _("You do not have permission to edit this track.") ?></h3> <?php } ?>
<?php
/*
if ($this->permissionDenied) {
echo(_("Viewing "));
} else {
echo(_("Editing "));
} */
?>
<div class="track-edit-right-wrapper">
<div class="track-edit-right">
<div class="inner_track_editor_title" style="width: 100%;">
<h2 style="line-height: 26px !important;"><span class="title_obj_name"><?php echo ($this->title); ?></span></h2>
<h3 style="line-height: 2px !important;"><span class=""><?php echo ($this->artist_name); ?></span></h3>
</div>
</div>
</div>
<div style="height: 160px;"></div>
<?php echo $this->form; ?>
<div class="track-edit-left">
<div class="artwork-upload" data-id="<?php echo ($this->id); ?>">
<div class="artwork-edit">
<input type='file' class="artworkUpload artwork-uploaded-<?php echo ($this->id); ?>" id="artworkUpload-<?php echo ($this->id); ?>" data-id="<?php echo ($this->id); ?>" accept=".png, .jpg, .jpeg" />
<label for="artworkUpload-<?php echo ($this->id); ?>"></label>
</div>
<div id="artwork-preview" class="artwork-preview">
<div class="artwork-preview-<?php echo ($this->id); ?>" id="artworkPreview" style="background-image: url(<?php echo $get_artwork; ?>);">
</div>
</div>
</div>
<div>
<a href="#" class="delete-artwork" data-id="<?php echo ($this->id); ?>" style="font-size: 11px;">Remove</a>
</div>
</div>
</div>
<div style="height: 160px;"></div>
<?php echo $this->form; ?>
<div class="collapsible-header closed"><span class="arrow-icon"></span><?php echo _("Visual Waveform Editor"); ?></div>
<div class="visual-waveform-editor" style="clear:both;padding:18px 24px 0 0;">
<div class="controls">
<div class="row">
<div class="col-sm-7">
<div class="btn-toolbar track-toolbar">
<div class="btn-group" title="Play Controls">
<button class="btn btn-control-player btn-new control-play-btn" id="track-play-<?php echo $this->id; ?>" onClick="wavesurfer['t<?php echo $this->id; ?>'].playPause();" style="background-color:#555555; border-top-left-radius: 5px; border-bottom-left-radius: 5px;">
<i class="icon-white icon-play"></i>
</button>
<button class="btn btn-control-player btn-new control-playedit-btn" id="track-playedit-<?php echo $this->id; ?>" onClick="" style="background-color:#555555; border-top-right-radius: 5px; border-bottom-right-radius: 5px">
<i class="icon-white icon-step-backward"></i>
</button>
</div>
<div class="btn-group" title="Playhead">
<div class="btn track-timer" style="border-radius: 5px">
<input class="track-timer-input" id="tracktimerinput-<?php echo $this->id; ?>" val="0.000" style="font-size:15px; font-weight:500;"/>
</div>
</div>
<div class="btn-group" title="Cue Controls">
<img src="<?php echo $baseUrl.'css/img/icon_cut_white.png';?>" style="top:6px;margin-right:5px;">
<button class="btn btn-control-player btn-new" id="cuein-set-<?php echo $this->id; ?>" style="background-color:#00e640; border-top-left-radius: 5px; border-bottom-left-radius: 5px;">
<i class="icon-white icon-chevron-right"></i>
</button>
<button class="btn btn-control-player btn-new" id="cueout-set-<?php echo $this->id; ?>" style="background-color:#f22613; border-top-right-radius: 5px; border-bottom-right-radius: 5px">
<i class="icon-white icon-chevron-left"></i>
</button>
</div>
<div class="btn-group pull-right zoom-container" title="Zoom">
<i class="icon-white icon-zoom-out" style="margin-top: -24px;"></i>
<input id="zoom-slider-<?php echo $this->id; ?>" class="input-slider" data-height="26" data-width="120" data-action="zoom-<?php echo $this->id; ?>" type="range" min="20" max="290" data-sprites="50" data-src="<?php echo $baseUrl.'css/images/slider.png'; ?>" value="0" />
<i class="icon-white icon-zoom-in" style="margin-top: -24px;"></i>
</div>
<br><br>
</div>
</div>
</div>
<div class="row">
<div id="track-waveform-<?php echo $this->id; ?>"></div>
<div id="timeline-<?php echo $this->id; ?>"></div>
</div>
<input type="hidden" id="volume-<?php echo $this->id; ?>" type="range" min="0" max="1" value="1" step="0.1">
</div>
</div>
</div>
</div>
<script>
//waveform
var track_id = <?php echo $this->id; ?>;
var selector_id = "#track-waveform-"+<?php echo $this->id; ?>;
var url = baseUrl + 'api/get-media/file/<?php echo $this->id; ?>';
var cuein = '<?php echo $this->cuein; ?>';
var cueout = '<?php echo $this->cueout; ?>';
var gain_level = deciSteps(<?php echo $this->replay_gain; ?>);
var default_gain = <?php echo $get_replay_gain; ?>;
var eTrack = renderWaveform(track_id, selector_id, url, cuein, cueout, gain_level, default_gain);
$(document).ready(function() {
$(".collapsible-header")
.off("click")
.on("click", function () {
$(this).toggleClass("visible");
$(".visual-waveform-editor").toggle();
$(".editor_pane_wrapper").animate({
scrollTop: $(".collapsible-header").offset().top * 2
}, 500);
});
// Counter field edit
$(document).on('change', '#tracktimerinput-<?php echo $this->id; ?>', 'input', function(event) {
event.preventDefault();
event.stopPropagation();
var val = $(this).val();
eTrack.setCurrentTime(val);
}).on('keypress keydown', '#tracktimerinput-<?php echo $this->id; ?>', 'input', function(event) {
if (event.key === 'Enter' || event.keyCode === 13 || event.keyCode === 10) {
var val = $(this).val();
eTrack.setCurrentTime(val);
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
return false;
}
})
//Gain Knob
$("#volume-<?php echo $this->id; ?>").val(deciSteps(<?php echo $this->replay_gain; ?>));
$(document).on('change', '.cuein_<?php echo $this->id; ?>', 'input', function(event) {
console.log('cuein input');
event.preventDefault();
event.stopPropagation();
var val = $(this).val();
var a = val.split(':');
var startseconds = (+a[0]) * 60 * 60 + (+a[1]) * 60 + (+a[2]);
console.log(startseconds);
var region = eTrack.regions.list[track_id];
region.update({start: startseconds});
$('#track-playedit-'+track_id).attr('onClick', 'wavesurfer["t'+ track_id +'"].play('+ region.start +', '+ region.end +');');
});
$(document).on('change', '.cueout_<?php echo $this->id; ?>', 'input', function(event) {
console.log('cueout input');
event.preventDefault();
event.stopPropagation();
var val = $(this).val();
var b = val.split(':');
var endseconds = (+b[0]) * 60 * 60 + (+b[1]) * 60 + (+b[2]);
var region = eTrack.regions.list[track_id];
region.update({end: endseconds});
$('#track-playedit-'+track_id).attr('onClick', 'wavesurfer["t'+ track_id +'"].play('+ region.start +', '+ region.end +');');
});
$(document).on('click', '#cuein-set-<?php echo $this->id; ?>', 'button', function(event) {
console.log('cuein button');
event.preventDefault();
event.stopPropagation();
var val = eTrack.getCurrentTime();
var region = eTrack.regions.list[track_id];
region.update({start: val});
document.getElementsByClassName("cuein_"+track_id)[0].value = toHHMMSS(region.start);
$('#track-playedit-'+track_id).attr('onClick', 'wavesurfer["t'+ track_id +'"].play('+ region.start +', '+ region.end +');');
});
$(document).on('click', '#cueout-set-<?php echo $this->id; ?>', 'button', function(event) {
console.log('cueout button')
event.preventDefault();
event.stopPropagation();
var val = eTrack.getCurrentTime();
var region = eTrack.regions.list[track_id];
region.update({end: val});
document.getElementsByClassName("cueout_"+track_id)[0].value = toHHMMSS(region.end);
$('#track-playedit-'+track_id).attr('onClick', 'wavesurfer["t'+ track_id +'"].play('+ region.start +', '+ region.end +');');
});
});
</script>