From: Banana Date: Thu, 8 Jul 2021 11:25:28 +0000 (+0200) Subject: adding musicbrainz as a grabber X-Git-Tag: 1.2~10 X-Git-Url: http://91.132.146.200/gitweb/?a=commitdiff_plain;h=4cdff6fe0555bb4788a3ea13ab2a52ac7927849b;p=bibliotheca-php.git adding musicbrainz as a grabber --- diff --git a/CHANGELOG b/CHANGELOG index ee0f4c8..2f9f71d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,7 @@ -1.x - NyLeve's Falls +1.x - NyLeve's Falls (TBD) + * Updated requirements information + * Added Musicbrainz grabber + * Added new field: artist 1.1 - Vortex Rikers 20210530 * Cleanup and merge to one config file. Read upgrade diff --git a/TODO b/TODO index 5640eda..24044d6 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,4 @@ +* User friendly setup * stats overview page. amount of entries. file / cache and db storage. Version info and where to find it. * User and groupmanagement: Check where a user or group is used! * Better error handling and display while adding / update and delete diff --git a/documentation/tool-imdbweb.txt b/documentation/tool-imdbweb.txt index e3c4b29..9ca69ae 100644 --- a/documentation/tool-imdbweb.txt +++ b/documentation/tool-imdbweb.txt @@ -12,4 +12,9 @@ Which fields are available for you to select, are configured in the config.imdbw easier since not every field the tool provides are needed. Follow the comments in the file for more details. -An option to make the target fields automatically selected by default to given values. +An option to make the target fields automatically selected by default to given values is also available +in the config file. + +# Setup +Rename the config-imdbweb.php.default to config-imdbweb.php in the config folder. +Follow the comments to update the settings. diff --git a/documentation/tool-musicbrainz.txt b/documentation/tool-musicbrainz.txt new file mode 100644 index 0000000..e69de29 diff --git a/upgrade/from-version-1.1.txt b/upgrade/from-version-1.1.txt new file mode 100644 index 0000000..baa84bf --- /dev/null +++ b/upgrade/from-version-1.1.txt @@ -0,0 +1,5 @@ +# DB changes. Run each line against your bibliotheca DB. +# Replace #REPLACEME# with your table prefix. Default is bib +INSERT INTO `#REPLACEME#_tool` (`id`, `name`, `description`, `action`, `target`, `owner`, `group`, `rights`) VALUES (NULL, 'Musicbrainz', 'Album infos', 'musicbrainz', '_self', '1', '1', 'rw-r--r--'); +INSERT INTO `#REPLACEME#_sys_fields` (`id`, `identifier`, `displayname`, `type`, `searchtype`, `createstring`, `inputValidation`, `value`, `apiinfo`, `created`, `modified`, `modificationuser`, `owner`, `group`, `rights`) VALUES (NULL, 'artist', 'Artist', 'text', 'entrySingleText', '`artists` varchar(128) NULL DEFAULT NULL', '', NULL, 'string 128', NOW(), NOW(), NULL, '1', '1', 'rw-r--r--'); +UPDATE `#REPLACEME#_sys_fields` SET `type` = 'year' WHERE `bib_sys_fields`.`id` = 14; diff --git a/webclient/config/config-imdbweb.php.default b/webclient/config/config-imdbweb.php.default index 187b95b..4f59616 100644 --- a/webclient/config/config-imdbweb.php.default +++ b/webclient/config/config-imdbweb.php.default @@ -1,6 +1,24 @@ 'title','date' => 'year', 'artist' => 'artist', 'image' => 'coverimage', 'tracks' => 'content', + 'runtime' => 'runtime' + ) +); diff --git a/webclient/lib/manageentry.class.php b/webclient/lib/manageentry.class.php index 76054a1..a16752a 100644 --- a/webclient/lib/manageentry.class.php +++ b/webclient/lib/manageentry.class.php @@ -563,14 +563,21 @@ class Manageentry { private function _saveField_selection($data, $queryData) { return $this->_saveField_text($data, $queryData); } + /** * Create part of the insert statement for field type year + * Uses some simple 4 digit patter to extract the year if the input is + * something like 2001-02-03 * * @param array $data Field data * @param array $queryData Query data array * @return array */ private function _saveField_year($data, $queryData) { + preg_match('/[0-9]{4}/', $data['valueToSave'], $matches); + if(isset($matches[0]) && !empty($matches[0])) { + $data['valueToSave'] = $matches[0]; + } return $this->_saveField_number($data, $queryData); } @@ -585,7 +592,7 @@ class Manageentry { private function _saveField_number($data, $queryData) { // make sure there is something (int) to save if(empty($data['valueToSave'])) { - $data['valueToSave'] = 0; + $data['valueToSave'] = 0; } $data['valueToSave'] = preg_replace('/[^\p{N}]/u', '', $data['valueToSave']); $queryData['init'][] = "`".$data['identifier']."` = '".$this->_DB->real_escape_string($data['valueToSave'])."'"; @@ -630,7 +637,7 @@ class Manageentry { * @param array $queryData * @return array */ - private function _saveField_upload($data, $queryData) { + private function _saveField_upload(array $data, array $queryData): array { $_up = $data['uploadData']; // delete the single upload @@ -651,7 +658,8 @@ class Manageentry { 'identifier' => $data['identifier'], 'name' => $newFilename, 'tmp_name' => $_up['tmp_name'][$data['identifier']], - 'multiple' => false + 'multiple' => false, + 'rebuildUpload' => $_up['rebuildUpload'][$data['identifier']] ); } return $queryData; @@ -664,7 +672,7 @@ class Manageentry { * @param array $queryData * @return array */ - private function _saveField_upload_multiple($data, $queryData) { + private function _saveField_upload_multiple(array $data, array $queryData): array { $_up = $data['uploadData']; if(isset($data['deleteData'])) { @@ -684,7 +692,8 @@ class Manageentry { 'identifier' => $data['identifier'], 'name' => $newFilename, 'tmp_name' => $_up['tmp_name'][$data['identifier']][$k], - 'multiple' => true + 'multiple' => true, + 'rebuildUpload' => $_up['rebuildUpload'][$data['identifier']][$k] ); } } @@ -723,7 +732,7 @@ class Manageentry { * @param string $insertId Number * @throws Exception */ - private function _runAfter_upload($uploadData, $insertId) { + private function _runAfter_upload(array $uploadData, string $insertId) { if(!empty($uploadData) && !empty($insertId)) { if(DEBUG) error_log("[DEBUG] ".__METHOD__." uploadata: ".var_export($uploadData,true)); $_path = PATH_STORAGE.'/'.$this->_collectionId.'/'.$insertId; @@ -758,7 +767,13 @@ class Manageentry { } if(isset($uploadData['tmp_name']) && isset($uploadData['name'])) { - if(!move_uploaded_file($uploadData['tmp_name'],$_path.'/'.$uploadData['identifier'].'-'.$uploadData['name'])) { + // special case if the image is already uploaded and not a real POST/FILES request + if(isset($uploadData['rebuildUpload']) && $uploadData['rebuildUpload'] === true) { + if(!rename($uploadData['tmp_name'],$_path.'/'.$uploadData['identifier'].'-'.$uploadData['name'])) { + throw new Exception("Can not rename file to: ".$_path.'/'.$uploadData['identifier'].'-'.$uploadData['name']); + } + } + elseif(!move_uploaded_file($uploadData['tmp_name'],$_path.'/'.$uploadData['identifier'].'-'.$uploadData['name'])) { throw new Exception("Can not move file to: ".$_path.'/'.$uploadData['identifier'].'-'.$uploadData['name']); } } @@ -773,7 +788,7 @@ class Manageentry { * @param $data array The entry data from getEditData * @return bool */ - private function _isOwner($data) { + private function _isOwner(array $data): bool { $ret = false; if($this->_User->param('isRoot')) { diff --git a/webclient/lib/musicbrainz.class.php b/webclient/lib/musicbrainz.class.php new file mode 100644 index 0000000..c7184ef --- /dev/null +++ b/webclient/lib/musicbrainz.class.php @@ -0,0 +1,329 @@ +_DEBUG = true; + } + + if(isset($options['resultLimit']) && !empty($options['resultLimit'])) { + $this->_resultLimit = $options['resultLimit']; + } + + $this->_BROWSER_AGENT = $options['browserAgent']; + $this->_BROWSER_LANG = $options['browserLang']; + $this->_BROWSER_ACCEPT = $options['browserAccept']; + } + + + /** + * Search for a release fpr the given artist and album name + * + * http://musicbrainz.org/ws/2/release/?query=artist:broilers%20AND%20release:muerte%20AND%20format:CD&fmt=json + * + * [releaseID] = title - artist - status - date - country - disambiguation - packaging - track-count + * + * + * @param string $artist The artist to search for + * @param string $album The album of the artist to search for + * + * @return array + */ + public function searchForRelease(string $artist, string $album): array { + $ret = array(); + + if(!empty($artist) && !empty($album)) { + $artist = urlencode($artist); + $album = urlencode($album); + $url = $this->_RELEASE_ENDPOINT; + $url .= '?&fmt=json&limit='.$this->_resultLimit.'&query='; + $url .= 'artist:'.$artist.'%20AND%20release:'.$album.'%20AND%20format:CD'; + + if(DEBUG) error_log("[DEBUG] musicbrainz release url: $url"); + + $do = $this->_curlCall($url); + $data = ''; + if(!empty($do)) { + $data = json_decode($do, true); + if(!empty($data)) { + if(DEBUG) error_log("[DEBUG] musicbrainz releases json data:".var_export($data,true)); + } + else { + error_log("[ERROR] musicbrainz invalid releases json data:".var_export($do,true)); + } + } + + if(!empty($data)) { + if(isset($data['releases'])) { + foreach($data['releases'] as $release) { + if(isset($release['title']) + && isset($release['status']) + && isset($release['date']) + && isset($release['country']) + && isset($release['artist-credit'][0]['name'])) { + + $ret[$release['id']] = $release['title'].' - '.$release['artist-credit'][0]['name'].'; '.$release['status'].'; '.$release['date'].'; '.$release['country']; + + if(isset($release['disambiguation'])) { + $ret[$release['id']] .= '; '.$release['disambiguation']; + } + if(isset($release['packaging'])) { + $ret[$release['id']] .= '; '.$release['packaging']; + } + + if(isset($release['track-count'])) { + $ret[$release['id']] .= '; tracks: '.$release['track-count']; + } + } + } + } + } + + } + + return $ret; + } + + /** + * Get the information from musicBrainz by given release ID + * https://musicbrainz.org/doc/MusicBrainz_API/Examples#Release + * + * http://musicbrainz.org/ws/2/release/59211ea4-ffd2-4ad9-9a4e-941d3148024a?inc=recordings&fmt=json + * + * [album] => title + * [date] => date + * [artist] => artist-credit name + * [tracks] => number - title - min + * [image] => img url + * [runtime] => summed up runtime in minutes from tracks + * + * @param string $releaseId + * @return array + */ + public function getReleaseInfo(string $releaseId): array { + $ret = array(); + + if(!empty($releaseId)) { + $url = $this->_RELEASE_ENDPOINT; + $url .= $releaseId; + $url .= '?&fmt=json&inc=recordings+artist-credits'; + + $do = $this->_curlCall($url); + $data = ''; + if(!empty($do)) { + $data = json_decode($do, true); + if(!empty($data)) { + if(DEBUG) error_log("[DEBUG] musicbrainz release json data:".var_export($data,true)); + } + else { + error_log("[ERROR] musicbrainz invalid release json data:".var_export($do,true)); + } + } + + if(!empty($data)) { + $ret['id'] = isset($data['id']) ? $data['id'] : ''; + $ret['album'] = isset($data['title']) ? $data['title'] : ''; + $ret['date'] = isset($data['date']) ? $data['date'] : ''; + $ret['artist'] = isset($data['artist-credit'][0]['name']) ? $data['artist-credit'][0]['name'] : ''; + $ret['tracks'] = ''; + $ret['image'] = ''; + $ret['runtime'] = 0; + foreach($data['media'][0]['tracks'] as $track) { + $ret['runtime'] += $track['length']; + $l = $track['length'] / 1000; + $l = date("i:s",$l); + $ret['tracks'] .= $track['number'].' - '.$track['title'].' - '.$l."\n"; + } + $ret['runtime'] = $ret['runtime'] / 1000; + $ret['runtime'] = date("i",$ret['runtime']); + + // image + $do = $this->_curlCall($this->_IMAGE_ENDPOINT.$releaseId); + if(!empty($do)) { + $imageData = json_decode($do, true); + if(!empty($imageData)) { + if(DEBUG) error_log("[DEBUG] image release json data:".var_export($imageData,true)); + $ret['image'] = isset($imageData['images'][0]['image']) ? $imageData['images'][0]['image'] : ''; + } + else { + error_log("[ERROR] image invalid release json data:".var_export($do,true)); + } + } + } + } + + return $ret; + } + + /** + * Download given URL to a tmp file + * make sure to remove the tmp file after use + * + * @param string $url + * @return string + */ + public function downloadCover(string $url): string { + $ret = ''; + + $_tmpFile = tempnam(sys_get_temp_dir(), "bibliotheca-"); + $fh = fopen($_tmpFile,"w+"); + if($fh !== false) { + + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_FILE, $fh); + + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, 3); + curl_setopt($ch, CURLOPT_USERAGENT, $this->_BROWSER_AGENT); + + if($this->_DEBUG) { + curl_setopt($ch, CURLOPT_VERBOSE, true); + curl_setopt($ch, CURLOPT_HEADERFUNCTION, + function($curl, $header) use (&$_headers) { + $len = strlen($header); + $header = explode(':', $header, 2); + if (count($header) < 2) { // ignore invalid headers + return $len; + } + $_headers[strtolower(trim($header[0]))][] = trim($header[1]); + return $len; + } + ); + } + + curl_exec($ch); + curl_close($ch); + + if($this->_DEBUG) { + error_log('[DEBUG] '.__METHOD__.' headers '.var_export($_headers,true)); + } + + $ret = $_tmpFile; + } + fclose($fh); + + return $ret; + } + + /** + * execute a curl call to the given $url + * + * @param string $url The request url + * @param integer $port + * @return string + */ + private function _curlCall(string $url, $port=80): string { + $ret = ''; + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_PORT, $port); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, 2); + curl_setopt($ch, CURLOPT_USERAGENT, $this->_BROWSER_AGENT); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Accept: '.$this->_BROWSER_ACCEPT, + 'Accept-Language: '.$this->_BROWSER_LANG) + ); + + if($this->_DEBUG) { + curl_setopt($ch, CURLOPT_VERBOSE, true); + curl_setopt($ch, CURLOPT_HEADERFUNCTION, + function($curl, $header) use (&$_headers) { + $len = strlen($header); + $header = explode(':', $header, 2); + if (count($header) < 2) { // ignore invalid headers + return $len; + } + $_headers[strtolower(trim($header[0]))][] = trim($header[1]); + return $len; + } + ); + } + + $do = curl_exec($ch); + if(is_string($do) === true) { + $ret = $do; + } + curl_close($ch); + + if($this->_DEBUG) { + error_log('[DEBUG] '.__METHOD__.' headers '.var_export($_headers,true)); + } + + return $ret; + } +} diff --git a/webclient/lib/summoner.class.php b/webclient/lib/summoner.class.php index 86133f4..ea684db 100644 --- a/webclient/lib/summoner.class.php +++ b/webclient/lib/summoner.class.php @@ -457,34 +457,7 @@ class Summoner { } /** - * execute a curl call to the fiven $url - * @param string $url The request url - * @param integer $port - * @return bool|string - */ - static function curlCall($url,$port=80) { - $ret = false; - - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15); - curl_setopt($ch, CURLOPT_TIMEOUT, 30); - curl_setopt($ch, CURLOPT_PORT, $port); - - $do = curl_exec($ch); - if(is_string($do) === true) { - $ret = $do; - } - - curl_close($ch); - - return $ret; - } - - /** - * check if a string strts with a given string + * check if a string starts with a given string * * @param string $haystack * @param string $needle diff --git a/webclient/view/default/manageentry/manageentry.php b/webclient/view/default/manageentry/manageentry.php index 3abed0a..b6fe15d 100644 --- a/webclient/view/default/manageentry/manageentry.php +++ b/webclient/view/default/manageentry/manageentry.php @@ -93,12 +93,12 @@ if(!empty($_collection)) { $_value = trim($fdata[$fieldData['identifier']]); $fieldData['valueToSave'] = trim($fdata[$fieldData['identifier']]); $_fieldsToSave[$fieldData['identifier']] = $fieldData; - } elseif(isset($fupload['name'][$fieldData['identifier']])) { + } elseif(isset($fupload['name'][$fieldData['identifier']])) { // special case upload if(isset($fdata[$fieldData['identifier']."_delete"])) { $fieldData['deleteData'] = $fdata[$fieldData['identifier']."_delete"]; } - // special case upload - // $_FILES data is combined + + // $_FILES data is combined if multiple $fieldData['uploadData'] = $fupload; $_fieldsToSave[$fieldData['identifier']] = $fieldData; diff --git a/webclient/view/default/tool/tool-imdbweb.php b/webclient/view/default/tool/tool-imdbweb.php index b3e3601..78aeaef 100644 --- a/webclient/view/default/tool/tool-imdbweb.php +++ b/webclient/view/default/tool/tool-imdbweb.php @@ -88,7 +88,7 @@ if(isset($_POST['submitFormSave'])) { if(!empty($_imdbId)) { try { - $IMDB->search($_imdbId); + $IMDB->search($_imdbId); // cache used } catch (Exception $e) { if(DEBUG) error_log("[DEBUG] imdb search catch: ".$e->getMessage()); diff --git a/webclient/view/default/tool/tool-musicbrainz.html b/webclient/view/default/tool/tool-musicbrainz.html new file mode 100644 index 0000000..4a813dd --- /dev/null +++ b/webclient/view/default/tool/tool-musicbrainz.html @@ -0,0 +1,75 @@ +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+ + +
+
+
+ + $v) { ?> + +
+ + +
+
+ +
+ +
+
+ + + +Musibrainz release page +
+ + $v) { ?> + +
+

+
+ +

+
+
+ +
+ + + +
+ +
+
+ diff --git a/webclient/view/default/tool/tool-musicbrainz.php b/webclient/view/default/tool/tool-musicbrainz.php new file mode 100644 index 0000000..a6f50fb --- /dev/null +++ b/webclient/view/default/tool/tool-musicbrainz.php @@ -0,0 +1,190 @@ + TOOL_BRAINZ_RESULT_LIMIT, + 'browserAgent' => TOOL_BRAINZ_BROWSER_AGENT, + 'browserLang' => TOOL_BRAINZ_BROWSER_ACCEPT_LANG, + 'browserAccept' => TOOL_BRAINZ_BROWSER_ACCEPT, + 'debug' => true +)); + +$TemplateData['releases'] = array(); +$TemplateData['release'] = array(); +$TemplateData['saveToSelection'] = ''; + +// prepare fields to save into selection +// create one time and then reuse it +$collectionFields = $ManangeCollectionsFields->getExistingFields(false, true); +if(!empty($collectionFields)) { + foreach ($collectionFields as $k=>$v) { + $TemplateData['saveToSelection'] .= "\n"; + } +} + +if(isset($_POST['submitFormSearch'])) { + $fdata = $_POST['fdata']; + if (!empty($fdata)) { + $artist = trim($fdata['artist']); + $artist = Summoner::validate($artist) ? $artist : false; + $album = trim($fdata['album']); + $album = Summoner::validate($album) ? $album : false; + + if(!empty($artist) && !empty($album)) { + $releaseSearch = $Brainz->searchForRelease($artist, $album); + + if(!empty($releaseSearch)) { + $TemplateData['releases'] = $releaseSearch; + } else { + $TemplateData['message']['content'] = "Nothing found."; + $TemplateData['message']['status'] = "error"; + } + } + else { + $TemplateData['message']['content'] = "Invalid search term"; + $TemplateData['message']['status'] = "error"; + } + } +} + +if(isset($_POST['submitFormReleaseSelect'])) { + if (isset($_POST['fdata'])) { + $fdata = $_POST['fdata']; + if (!empty($fdata)) { + $releaseId = $fdata['rselect']; + if(!empty($releaseId)) { + $releaseInfo = $Brainz->getReleaseInfo($releaseId); + if(!empty($releaseInfo)) { + $TemplateData['release'] = $releaseInfo; + } else { + $TemplateData['message']['content'] = "Nothing found."; + $TemplateData['message']['status'] = "error"; + } + } + } + } + else { + $TemplateData['message']['content'] = "Invalid selection"; + $TemplateData['message']['status'] = "error"; + } +} + +if(isset($_POST['submitFormSave'])) { + $fdata = $_POST['fdata']; + if (!empty($fdata)) { + + // build data array based on submit + // see creation log for structure + $_data = array(); + foreach($fdata['into'] as $k=>$v) { + if(!empty($v) && isset($fdata['from'][$k])) { + if(isset($collectionFields[$v])) { + + $_data[$v] = $collectionFields[$v]; + $_data[$v]['valueToSave'] = $fdata['from'][$k]; + + // special case for image + if($k == "image") { + + $fieldData = array(); + + $_f = $Brainz->downloadCover($fdata['from'][$k]); + if($_f && is_file($_f)) { + $_e = UPLOAD_ERR_OK; + // build _FILES based on regular add form + $fieldData['name'][$_data[$v]['identifier']] = 'cover.jpg'; + $fieldData['type'][$_data[$v]['identifier']] = mime_content_type($_f); + $fieldData['size'][$_data[$v]['identifier']] = filesize($_f); + $fieldData['tmp_name'][$_data[$v]['identifier']] = $_f; + $fieldData['error'][$_data[$v]['identifier']] = UPLOAD_ERR_OK; + $fieldData['rebuildUpload'][$_data[$v]['identifier']] = true; + } + + + $_data[$v]['uploadData'] = $fieldData; + } + } + } + } + + $_r = $Tools->getDefaultCreationInfo(); + if(!empty($TemplateData['editEntry'])) { + // update existing one + $do = $Manageentry->create($_data, + $_r['id'], + $_r['group'], + $_r['rights'], + $TemplateData['editEntry']['id'] + ); + $TemplateData['message']['content'] = "Date saved successfully"; + } + else { + // create into loaded collection + $do = $Manageentry->create($_data, + $_r['id'], + $_r['group'], + $_r['rights'] + ); + $TemplateData['message']['content'] = "Date saved successfully: + Here"; + } + + if(!empty($do)) { + $TemplateData['message']['status'] = "success"; + } + else { + $TemplateData['message']['content'] = "Data could not be saved. See logs for more."; + $TemplateData['message']['status'] = "error"; + } + } +} + +/** + * Helper function. Takes the prebuild options for the target selection field and search for a matching key. + * Since the optionString is prebuild, avoiding looping over and over again, the selection needs to be done + * by search and replace. + * Checks if TOOL_BRAINZ_FIELDS_TO is defined and a matching key=>value pair is available + * + * @param string $optionString + * @param string $key + * @return string + */ +function toolMethod_GetTargetSelection(string $optionString, string $key): string { + if(defined('TOOL_BRAINZ_FIELDS_TO') & !empty($key)) { + if(isset(TOOL_BRAINZ_FIELDS_TO[$key])) { + $_k = "sel_".TOOL_BRAINZ_FIELDS_TO[$key]; + $optionString = str_replace($_k,'selected="selected"',$optionString); + } + } + + return $optionString; +}