{"action":"created","comment":{"url":"https://api.github.com/repos/europeana/metis-framework/pulls/comments/177759907","pull_request_review_id":107679314,"id":177759907,"diff_hunk":"@@ -0,0 +1,441 @@\n+package eu.europeana.metis.mediaservice;\n+\n+import java.io.Closeable;\n+import java.io.File;\n+import java.io.FileOutputStream;\n+import java.io.IOException;\n+import java.io.InputStream;\n+import java.io.OutputStream;\n+import java.net.URI;\n+import java.nio.file.FileAlreadyExistsException;\n+import java.nio.file.Files;\n+import java.util.ArrayList;\n+import java.util.Arrays;\n+import java.util.Collection;\n+import java.util.Collections;\n+import java.util.HashMap;\n+import java.util.Iterator;\n+import java.util.List;\n+import java.util.Map;\n+import java.util.concurrent.ExecutorService;\n+import java.util.concurrent.Executors;\n+import java.util.regex.Matcher;\n+import java.util.regex.Pattern;\n+import java.util.stream.Collectors;\n+\n+import org.apache.commons.codec.digest.DigestUtils;\n+import org.apache.commons.io.FileUtils;\n+import org.apache.commons.io.IOUtils;\n+import org.apache.commons.lang3.StringUtils;\n+import org.apache.pdfbox.cos.COSName;\n+import org.apache.pdfbox.pdmodel.PDDocument;\n+import org.apache.pdfbox.pdmodel.PDResources;\n+import org.apache.pdfbox.pdmodel.common.PDMetadata;\n+import org.apache.pdfbox.pdmodel.graphics.PDXObject;\n+import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;\n+import org.apache.pdfbox.text.PDFTextStripper;\n+import org.apache.tika.Tika;\n+import org.json.JSONObject;\n+import org.json.JSONTokener;\n+import org.slf4j.Logger;\n+import org.slf4j.LoggerFactory;\n+\n+public class MediaProcessor implements Closeable {\n+\t\n+\tprivate static final Logger log = LoggerFactory.getLogger(MediaProcessor.class);\n+\t\n+\tprivate static final int[] THUMB_SIZE = { 200, 400 };\n+\tprivate static final String[] THUMB_SUFFIX = { \"-MEDIUM\", \"-LARGE\" };\n+\t\n+\tprivate static File tempDir;\n+\tprivate static File colormapFile;\n+\tprivate static String magickCmd;\n+\tprivate static String ffprobeCmd;\n+\tprivate static Tika tika;\n+\t\n+\tprivate ExecutorService commandIOThreadPool = Executors.newFixedThreadPool(2);\n+\t\n+\tprivate EdmObject edm;\n+\tprivate Map<String, List<UrlType>> urlTypes;\n+\tprivate Map<File, String> thumbnails = new HashMap<>();\n+\t\n+\tpublic MediaProcessor() {\n+\t\tstaticInit();\n+\t}\n+\t\n+\t/**\n+\t * @param edm future calls to {@link #processResource(String, String, File)}\n+\t *        will store extracted metadata in given EDM.\n+\t */\n+\tpublic void setEdm(EdmObject edm) {\n+\t\tthis.edm = edm;\n+\t\turlTypes = edm.getResourceUrls(Arrays.asList(UrlType.values()));\n+\t\tthumbnails.clear();\n+\t}\n+\t\n+\tpublic EdmObject getEdm() {\n+\t\treturn edm;\n+\t}\n+\t\n+\t/**\n+\t * @param contents downloaded file, can be {@code null} for mime types accepted\n+\t *        by {@link #supportsLinkProcessing(String)}\n+\t */\n+\tpublic void processResource(String url, String providedMimeType, File contents) throws MediaException {\n+\t\tString mimeType;\n+\t\ttry {\n+\t\t\tmimeType = contents != null ? tika.detect(contents) : tika.detect(URI.create(url).toURL());\n+\t\t} catch (IOException e) {\n+\t\t\tthrow new MediaException(\"Mime type checking error\", \"IOException \" + e.getMessage(), e, contents == null);\n+\t\t}\n+\t\t\n+\t\tif (!mimeType.equals(providedMimeType))\n+\t\t\tlog.info(\"Invalid mime type provided (should be {}, was {}): {}\", mimeType, providedMimeType, url);\n+\t\tif (contents == null && !supportsLinkProcessing(mimeType))\n+\t\t\tthrow new IllegalArgumentException(\"Contents file is required for mime type \" + mimeType);\n+\t\t\n+\t\ttry {\n+\t\t\tif (isImage(mimeType))\n+\t\t\t\tprocessImage(url, mimeType, contents);\n+\t\t\tif (isAudioVideo(mimeType))\n+\t\t\t\tprocessAudioVideo(url, mimeType, contents);\n+\t\t\tif (isText(mimeType))\n+\t\t\t\tprocessText(url, mimeType, contents);\n+\t\t} catch (IOException e) {\n+\t\t\tthrow new MediaException(\"I/O error during procesing\", \"IOException \" + e.getMessage(), e);\n+\t\t}\n+\t}\n+\t\n+\t/**\n+\t * @return thumbnails for all the image resources processed since the last call\n+\t *         to {@link #setEdm(EdmObject)}. The map's key is the thumbnail file,\n+\t *         with the name it should be stored under. The value is the resource's\n+\t *         original url. Remember to remove the files when they are no longer\n+\t *         needed.\n+\t */\n+\tpublic Map<File, String> getThumbnails() {\n+\t\treturn new HashMap<>(thumbnails);\n+\t}\n+\t\n+\t@Override\n+\tpublic void close() {\n+\t\tcommandIOThreadPool.shutdown();\n+\t}\n+\t\n+\tprivate void processImage(String url, String mimeType, File content) throws MediaException, IOException {\n+\t\tList<File> thumbs = prepareThumbnailFiles(url, mimeType);\n+\t\t\n+\t\tint sizes = THUMB_SIZE.length;\n+\t\tArrayList<String> command = new ArrayList<>(Arrays.asList(\n+\t\t\t\tmagickCmd, content.getPath() + \"[0]\", \"-format\", \"%w\\n%h\\n%[colorspace]\\n\", \"-write\", \"info:\"));\n+\t\tif (\"application/pdf\".equals(mimeType))\n+\t\t\tcommand.addAll(Arrays.asList(\"-background\", \"white\", \"-alpha\", \"remove\"));\n+\t\tfor (int i = 0; i < sizes - 1; i++) {\n+\t\t\tcommand.addAll(Arrays.asList(\n+\t\t\t\t\t\"(\", \"+clone\", \"-thumbnail\", THUMB_SIZE[i] + \"x\", \"-write\", thumbs.get(i).getPath(), \"+delete\",\n+\t\t\t\t\t\")\"));\n+\t\t} // do not +delete the last one, use it to find dominant colors (smaller=quicker)\n+\t\tcommand.addAll(Arrays.asList(\n+\t\t\t\t\"-thumbnail\", THUMB_SIZE[sizes - 1] + \"x\", \"-write\", thumbs.get(sizes - 1).getPath()));\n+\t\tcommand.addAll(Arrays.asList(\n+\t\t\t\t\"-colorspace\", \"sRGB\", \"-dither\", \"Riemersma\", \"-remap\", colormapFile.getPath(),\n+\t\t\t\t\"-format\", \"\\n%c\", \"histogram:info:\"));\n+\t\t\n+\t\tList<String> results = runCommand(command, false);\n+\t\t\n+\t\tint width;\n+\t\ttry {\n+\t\t\twidth = Integer.parseInt(results.get(0));\n+\t\t\tint height = Integer.parseInt(results.get(1));\n+\t\t\tif (shouldExtractMetadata(urlTypes.get(url))) {\n+\t\t\t\tWebResource resource = edm.getWebResource(url);\n+\t\t\t\tresource.setMimeType(mimeType);\n+\t\t\t\tresource.setFileSize(content.length());\n+\t\t\t\tresource.setWidth(width);\n+\t\t\t\tresource.setHeight(height);\n+\t\t\t\tresource.setOrientation(width > height);\n+\t\t\t\tresource.setColorspace(results.get(2));\n+\t\t\t\tresource.setDominantColors(extractDominantColors(results));\n+\t\t\t}\n+\t\t} catch (Exception e) {\n+\t\t\tlog.info(\"Could not parse ImageMagick response:\\n\" + StringUtils.join(results, \"\\n\"), e);\n+\t\t\tthrow new MediaException(\"File seems to be corrupted\", \"IMAGE ERROR\");\n+\t\t}\n+\t\t\n+\t\tfor (int i = 0; i < sizes; i++) {\n+\t\t\tFile thumb = thumbs.get(i);\n+\t\t\tif (thumb.length() == 0)\n+\t\t\t\tthrow new MediaException(\"Thumbnail file empty: \" + thumb, \"THUMBNAIL ERROR\");\n+\t\t\tif (width < THUMB_SIZE[i]) {\n+\t\t\t\tFileUtils.copyFile(content, thumb);\n+\t\t\t}\n+\t\t\tthumbnails.put(thumb, url);\n+\t\t}\n+\t}\n+\t\n+\t/**\n+\t * Thumbnails are put directly in temp dir with names that should be used in the\n+\t * target storage. It's a problem when multiple EDMs share the same resource -\n+\t * we can't use the same thumbnail files because system won't know when they can\n+\t * be removed. So duplicate thumbnails are stored in subdirectories with\n+\t * increasing numbers.\n+\t */\n+\tprivate List<File> prepareThumbnailFiles(String url, String mimeType) throws IOException {\n+\t\tList<File> thumbs = new ArrayList<>();\n+\t\tString md5 = DigestUtils.md5Hex(url);\n+\t\tString ext = Arrays.asList(\"application/pdf\", \"image/png\").contains(mimeType) ? \".png\" : \".jpeg\";\n+\t\tfor (int i = 0; i < THUMB_SUFFIX.length; i++) {\n+\t\t\tFile temp = File.createTempFile(\"thumb\", null);\n+\t\t\tfor (int j = 0; j < 1000; j++) {\n+\t\t\t\tFile dir = j == 0 ? tempDir : new File(tempDir, \"media_thumbnail_duplicates_\" + j);\n+\t\t\t\tif (!dir.isDirectory() && !dir.mkdir())\n+\t\t\t\t\tthrow new IOException(\"Could not create thumbnails subdirectory: \" + dir);\n+\t\t\t\ttry {\n+\t\t\t\t\tFile f = new File(dir, md5 + THUMB_SUFFIX[i] + ext);\n+\t\t\t\t\tFiles.move(temp.toPath(), f.toPath());\n+\t\t\t\t\tthumbs.add(f);\n+\t\t\t\t\tbreak;\n+\t\t\t\t} catch (FileAlreadyExistsException e) {\n+\t\t\t\t\tlog.trace(j + \" duplicates of \" + url, e);\n+\t\t\t\t\t// try next dir\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tif (thumbs.size() < i + 1) {\n+\t\t\t\tif (!temp.delete())\n+\t\t\t\t\tlog.warn(\"Could not remove temp file \" + temp);\n+\t\t\t\tthrow new IOException(\"Too many duplicates of url \" + url);\n+\t\t\t}\n+\t\t}\n+\t\treturn thumbs;\n+\t}\n+\t\n+\tprivate List<String> extractDominantColors(List<String> results) {\n+\t\tfinal Pattern pattern = Pattern.compile(\"#([0-9A-F]{6})\");\n+\t\treturn results.stream()\n+\t\t\t\t.skip(3)\n+\t\t\t\t.sorted(Collections.reverseOrder())\n+\t\t\t\t.limit(6)\n+\t\t\t\t.map(line -> {\n+\t\t\t\t\tMatcher m = pattern.matcher(line);\n+\t\t\t\t\tm.find();\n+\t\t\t\t\treturn m.group(1); // throw exception if not found\n+\t\t\t\t})\n+\t\t\t\t.collect(Collectors.toList());\n+\t}\n+\t\n+\tprivate void processAudioVideo(String url, String mimeType, File contents) throws MediaException, IOException {\n+\t\tif (!shouldExtractMetadata(urlTypes.get(url)))\n+\t\t\treturn;\n+\t\t\n+\t\tList<String> command = Arrays.asList(ffprobeCmd, \"-v\", \"quiet\", \"-print_format\", \"json\",\n+\t\t\t\t\"-show_format\", \"-show_streams\", \"-hide_banner\", contents != null ? contents.getPath() : url);\n+\t\tList<String> resultLines = runCommand(command, false);\n+\t\t\n+\t\ttry {\n+\t\t\tJSONObject result = new JSONObject(new JSONTokener(String.join(\"\", resultLines)));\n+\t\t\tif (result.length() == 0 && contents == null) {\n+\t\t\t\tthrow new MediaException(\"Probably downlaod failed\", \"\", null, true);\n+\t\t\t}\n+\t\t\tWebResource resource = edm.getWebResource(url);\n+\t\t\tresource.setMimeType(mimeType);\n+\t\t\tresource.setFileSize(result.getJSONObject(\"format\").getLong(\"size\"));\n+\t\t\t\n+\t\t\tJSONObject videoStream = null;\n+\t\t\tJSONObject audioStream = null;\n+\t\t\tfor (Object streamObject : result.getJSONArray(\"streams\")) {\n+\t\t\t\tJSONObject stream = (JSONObject) streamObject;\n+\t\t\t\tif (videoStream == null && \"video\".equals(stream.getString(\"codec_type\")))\n+\t\t\t\t\tvideoStream = stream;\n+\t\t\t\tif (audioStream == null && \"audio\".equals(stream.getString(\"codec_type\")))\n+\t\t\t\t\taudioStream = stream;\n+\t\t\t}\n+\t\t\tif (videoStream != null) {\n+\t\t\t\tresource.setDuration(videoStream.getDouble(\"duration\"));\n+\t\t\t\tresource.setBitrate(videoStream.getInt(\"bit_rate\"));\n+\t\t\t\tresource.setWidth(videoStream.getInt(\"width\"));\n+\t\t\t\tresource.setHeight(videoStream.getInt(\"height\"));\n+\t\t\t\tresource.setCodecName(videoStream.getString(\"codec_name\"));\n+\t\t\t\t\n+\t\t\t\tString[] frameRateParts = videoStream.getString(\"avg_frame_rate\").split(\"/\");\n+\t\t\t\tdouble frameRate = Double.parseDouble(frameRateParts[0]) / Double.parseDouble(frameRateParts[1]);\n+\t\t\t\tresource.setFrameRete(frameRate);\n+\t\t\t} else if (audioStream != null) {\n+\t\t\t\tresource.setDuration(audioStream.getDouble(\"duration\"));\n+\t\t\t\tresource.setBitrate(audioStream.getInt(\"bit_rate\"));\n+\t\t\t\tresource.setCahhnels(audioStream.getInt(\"channels\"));\n+\t\t\t\tresource.setSampleRate(audioStream.getInt(\"sample_rate\"));\n+\t\t\t\tresource.setSampleSize(audioStream.getInt(\"bits_per_sample\"));\n+\t\t\t} else {\n+\t\t\t\tthrow new Exception(\"No media streams\");\n+\t\t\t}\n+\t\t} catch (MediaException e) {\n+\t\t\tthrow e;\n+\t\t} catch (Exception e) {\n+\t\t\tlog.info(\"Could not parse ffprobe response:\\n\" + StringUtils.join(resultLines, \"\\n\"), e);\n+\t\t\tthrow new MediaException(\"File seems to be corrupted\", \"AUDIOVIDEO ERROR\");\n+\t\t}\n+\t}\n+\t\n+\tprivate void processText(String url, String mimeType, File contents) throws IOException {\n+\t\tif (!shouldExtractMetadata(urlTypes.get(url)))\n+\t\t\treturn;\n+\t\t\n+\t\tboolean containsText = mimeType.startsWith(\"text/\");\n+\t\tInteger resolution = null;\n+\t\t\n+\t\tif (mimeType.equals(\"application/pdf\")) {","path":"metis-media-service/src/main/java/eu/europeana/metis/mediaservice/MediaProcessor.java","position":286,"original_position":286,"commit_id":"8fbf56a1135058a24c0d74e67ba835689ea5931d","original_commit_id":"8fbf56a1135058a24c0d74e67ba835689ea5931d","user":{"login":"stzanakis","id":6573071,"avatar_url":"https://avatars2.githubusercontent.com/u/6573071?v=4","gravatar_id":"","url":"https://api.github.com/users/stzanakis","html_url":"https://github.com/stzanakis","followers_url":"https://api.github.com/users/stzanakis/followers","following_url":"https://api.github.com/users/stzanakis/following{/other_user}","gists_url":"https://api.github.com/users/stzanakis/gists{/gist_id}","starred_url":"https://api.github.com/users/stzanakis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stzanakis/subscriptions","organizations_url":"https://api.github.com/users/stzanakis/orgs","repos_url":"https://api.github.com/users/stzanakis/repos","events_url":"https://api.github.com/users/stzanakis/events{/privacy}","received_events_url":"https://api.github.com/users/stzanakis/received_events","type":"User","site_admin":false},"body":"Also this if has too many nested flow statements if, for, try etc. We can split it down I think. \r\nPlease refactor code to remove the label breaks as well.","created_at":"2018-03-28T14:02:14Z","updated_at":"2018-03-28T14:30:37Z","html_url":"https://github.com/europeana/metis-framework/pull/136#discussion_r177759907","pull_request_url":"https://api.github.com/repos/europeana/metis-framework/pulls/136","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/europeana/metis-framework/pulls/comments/177759907"},"html":{"href":"https://github.com/europeana/metis-framework/pull/136#discussion_r177759907"},"pull_request":{"href":"https://api.github.com/repos/europeana/metis-framework/pulls/136"}},"in_reply_to_id":177758362},"pull_request":{"url":"https://api.github.com/repos/europeana/metis-framework/pulls/136","id":177654559,"html_url":"https://github.com/europeana/metis-framework/pull/136","diff_url":"https://github.com/europeana/metis-framework/pull/136.diff","patch_url":"https://github.com/europeana/metis-framework/pull/136.patch","issue_url":"https://api.github.com/repos/europeana/metis-framework/issues/136","number":136,"state":"open","locked":false,"title":"Feat/met 737 create library for media service","user":{"login":"mmatela","id":687761,"avatar_url":"https://avatars2.githubusercontent.com/u/687761?v=4","gravatar_id":"","url":"https://api.github.com/users/mmatela","html_url":"https://github.com/mmatela","followers_url":"https://api.github.com/users/mmatela/followers","following_url":"https://api.github.com/users/mmatela/following{/other_user}","gists_url":"https://api.github.com/users/mmatela/gists{/gist_id}","starred_url":"https://api.github.com/users/mmatela/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mmatela/subscriptions","organizations_url":"https://api.github.com/users/mmatela/orgs","repos_url":"https://api.github.com/users/mmatela/repos","events_url":"https://api.github.com/users/mmatela/events{/privacy}","received_events_url":"https://api.github.com/users/mmatela/received_events","type":"User","site_admin":false},"body":"@stzanakis  @jochen-vermeulen","created_at":"2018-03-27T07:37:58Z","updated_at":"2018-03-28T14:30:38Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2fd271f49b3358a9f1f3912c4e6b6e69c43d491f","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/europeana/metis-framework/pulls/136/commits","review_comments_url":"https://api.github.com/repos/europeana/metis-framework/pulls/136/comments","review_comment_url":"https://api.github.com/repos/europeana/metis-framework/pulls/comments{/number}","comments_url":"https://api.github.com/repos/europeana/metis-framework/issues/136/comments","statuses_url":"https://api.github.com/repos/europeana/metis-framework/statuses/8fbf56a1135058a24c0d74e67ba835689ea5931d","head":{"label":"europeana:feat/MET-737_create_library_for_media_service","ref":"feat/MET-737_create_library_for_media_service","sha":"8fbf56a1135058a24c0d74e67ba835689ea5931d","user":{"login":"europeana","id":4570681,"avatar_url":"https://avatars3.githubusercontent.com/u/4570681?v=4","gravatar_id":"","url":"https://api.github.com/users/europeana","html_url":"https://github.com/europeana","followers_url":"https://api.github.com/users/europeana/followers","following_url":"https://api.github.com/users/europeana/following{/other_user}","gists_url":"https://api.github.com/users/europeana/gists{/gist_id}","starred_url":"https://api.github.com/users/europeana/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/europeana/subscriptions","organizations_url":"https://api.github.com/users/europeana/orgs","repos_url":"https://api.github.com/users/europeana/repos","events_url":"https://api.github.com/users/europeana/events{/privacy}","received_events_url":"https://api.github.com/users/europeana/received_events","type":"Organization","site_admin":false},"repo":{"id":51921813,"name":"metis-framework","full_name":"europeana/metis-framework","owner":{"login":"europeana","id":4570681,"avatar_url":"https://avatars3.githubusercontent.com/u/4570681?v=4","gravatar_id":"","url":"https://api.github.com/users/europeana","html_url":"https://github.com/europeana","followers_url":"https://api.github.com/users/europeana/followers","following_url":"https://api.github.com/users/europeana/following{/other_user}","gists_url":"https://api.github.com/users/europeana/gists{/gist_id}","starred_url":"https://api.github.com/users/europeana/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/europeana/subscriptions","organizations_url":"https://api.github.com/users/europeana/orgs","repos_url":"https://api.github.com/users/europeana/repos","events_url":"https://api.github.com/users/europeana/events{/privacy}","received_events_url":"https://api.github.com/users/europeana/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/europeana/metis-framework","description":"Metis, named after the Titaness of Wisdom, is our in-development data publication framework including both a client application and a number of data processing (micro)services","fork":false,"url":"https://api.github.com/repos/europeana/metis-framework","forks_url":"https://api.github.com/repos/europeana/metis-framework/forks","keys_url":"https://api.github.com/repos/europeana/metis-framework/keys{/key_id}","collaborators_url":"https://api.github.com/repos/europeana/metis-framework/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/europeana/metis-framework/teams","hooks_url":"https://api.github.com/repos/europeana/metis-framework/hooks","issue_events_url":"https://api.github.com/repos/europeana/metis-framework/issues/events{/number}","events_url":"https://api.github.com/repos/europeana/metis-framework/events","assignees_url":"https://api.github.com/repos/europeana/metis-framework/assignees{/user}","branches_url":"https://api.github.com/repos/europeana/metis-framework/branches{/branch}","tags_url":"https://api.github.com/repos/europeana/metis-framework/tags","blobs_url":"https://api.github.com/repos/europeana/metis-framework/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/europeana/metis-framework/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/europeana/metis-framework/git/refs{/sha}","trees_url":"https://api.github.com/repos/europeana/metis-framework/git/trees{/sha}","statuses_url":"https://api.github.com/repos/europeana/metis-framework/statuses/{sha}","languages_url":"https://api.github.com/repos/europeana/metis-framework/languages","stargazers_url":"https://api.github.com/repos/europeana/metis-framework/stargazers","contributors_url":"https://api.github.com/repos/europeana/metis-framework/contributors","subscribers_url":"https://api.github.com/repos/europeana/metis-framework/subscribers","subscription_url":"https://api.github.com/repos/europeana/metis-framework/subscription","commits_url":"https://api.github.com/repos/europeana/metis-framework/commits{/sha}","git_commits_url":"https://api.github.com/repos/europeana/metis-framework/git/commits{/sha}","comments_url":"https://api.github.com/repos/europeana/metis-framework/comments{/number}","issue_comment_url":"https://api.github.com/repos/europeana/metis-framework/issues/comments{/number}","contents_url":"https://api.github.com/repos/europeana/metis-framework/contents/{+path}","compare_url":"https://api.github.com/repos/europeana/metis-framework/compare/{base}...{head}","merges_url":"https://api.github.com/repos/europeana/metis-framework/merges","archive_url":"https://api.github.com/repos/europeana/metis-framework/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/europeana/metis-framework/downloads","issues_url":"https://api.github.com/repos/europeana/metis-framework/issues{/number}","pulls_url":"https://api.github.com/repos/europeana/metis-framework/pulls{/number}","milestones_url":"https://api.github.com/repos/europeana/metis-framework/milestones{/number}","notifications_url":"https://api.github.com/repos/europeana/metis-framework/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/europeana/metis-framework/labels{/name}","releases_url":"https://api.github.com/repos/europeana/metis-framework/releases{/id}","deployments_url":"https://api.github.com/repos/europeana/metis-framework/deployments","created_at":"2016-02-17T12:43:34Z","updated_at":"2018-03-26T13:43:29Z","pushed_at":"2018-03-28T12:50:30Z","git_url":"git://github.com/europeana/metis-framework.git","ssh_url":"git@github.com:europeana/metis-framework.git","clone_url":"https://github.com/europeana/metis-framework.git","svn_url":"https://github.com/europeana/metis-framework","homepage":"","size":127344,"stargazers_count":7,"watchers_count":7,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"archived":false,"open_issues_count":4,"license":{"key":"eupl-1.1","name":"European Union Public License 1.1","spdx_id":"EUPL-1.1","url":"https://api.github.com/licenses/eupl-1.1"},"forks":1,"open_issues":4,"watchers":7,"default_branch":"develop"}},"base":{"label":"europeana:develop","ref":"develop","sha":"6a8d3ba72f55c8b863c4680cc1d22cd91e29f885","user":{"login":"europeana","id":4570681,"avatar_url":"https://avatars3.githubusercontent.com/u/4570681?v=4","gravatar_id":"","url":"https://api.github.com/users/europeana","html_url":"https://github.com/europeana","followers_url":"https://api.github.com/users/europeana/followers","following_url":"https://api.github.com/users/europeana/following{/other_user}","gists_url":"https://api.github.com/users/europeana/gists{/gist_id}","starred_url":"https://api.github.com/users/europeana/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/europeana/subscriptions","organizations_url":"https://api.github.com/users/europeana/orgs","repos_url":"https://api.github.com/users/europeana/repos","events_url":"https://api.github.com/users/europeana/events{/privacy}","received_events_url":"https://api.github.com/users/europeana/received_events","type":"Organization","site_admin":false},"repo":{"id":51921813,"name":"metis-framework","full_name":"europeana/metis-framework","owner":{"login":"europeana","id":4570681,"avatar_url":"https://avatars3.githubusercontent.com/u/4570681?v=4","gravatar_id":"","url":"https://api.github.com/users/europeana","html_url":"https://github.com/europeana","followers_url":"https://api.github.com/users/europeana/followers","following_url":"https://api.github.com/users/europeana/following{/other_user}","gists_url":"https://api.github.com/users/europeana/gists{/gist_id}","starred_url":"https://api.github.com/users/europeana/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/europeana/subscriptions","organizations_url":"https://api.github.com/users/europeana/orgs","repos_url":"https://api.github.com/users/europeana/repos","events_url":"https://api.github.com/users/europeana/events{/privacy}","received_events_url":"https://api.github.com/users/europeana/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/europeana/metis-framework","description":"Metis, named after the Titaness of Wisdom, is our in-development data publication framework including both a client application and a number of data processing (micro)services","fork":false,"url":"https://api.github.com/repos/europeana/metis-framework","forks_url":"https://api.github.com/repos/europeana/metis-framework/forks","keys_url":"https://api.github.com/repos/europeana/metis-framework/keys{/key_id}","collaborators_url":"https://api.github.com/repos/europeana/metis-framework/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/europeana/metis-framework/teams","hooks_url":"https://api.github.com/repos/europeana/metis-framework/hooks","issue_events_url":"https://api.github.com/repos/europeana/metis-framework/issues/events{/number}","events_url":"https://api.github.com/repos/europeana/metis-framework/events","assignees_url":"https://api.github.com/repos/europeana/metis-framework/assignees{/user}","branches_url":"https://api.github.com/repos/europeana/metis-framework/branches{/branch}","tags_url":"https://api.github.com/repos/europeana/metis-framework/tags","blobs_url":"https://api.github.com/repos/europeana/metis-framework/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/europeana/metis-framework/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/europeana/metis-framework/git/refs{/sha}","trees_url":"https://api.github.com/repos/europeana/metis-framework/git/trees{/sha}","statuses_url":"https://api.github.com/repos/europeana/metis-framework/statuses/{sha}","languages_url":"https://api.github.com/repos/europeana/metis-framework/languages","stargazers_url":"https://api.github.com/repos/europeana/metis-framework/stargazers","contributors_url":"https://api.github.com/repos/europeana/metis-framework/contributors","subscribers_url":"https://api.github.com/repos/europeana/metis-framework/subscribers","subscription_url":"https://api.github.com/repos/europeana/metis-framework/subscription","commits_url":"https://api.github.com/repos/europeana/metis-framework/commits{/sha}","git_commits_url":"https://api.github.com/repos/europeana/metis-framework/git/commits{/sha}","comments_url":"https://api.github.com/repos/europeana/metis-framework/comments{/number}","issue_comment_url":"https://api.github.com/repos/europeana/metis-framework/issues/comments{/number}","contents_url":"https://api.github.com/repos/europeana/metis-framework/contents/{+path}","compare_url":"https://api.github.com/repos/europeana/metis-framework/compare/{base}...{head}","merges_url":"https://api.github.com/repos/europeana/metis-framework/merges","archive_url":"https://api.github.com/repos/europeana/metis-framework/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/europeana/metis-framework/downloads","issues_url":"https://api.github.com/repos/europeana/metis-framework/issues{/number}","pulls_url":"https://api.github.com/repos/europeana/metis-framework/pulls{/number}","milestones_url":"https://api.github.com/repos/europeana/metis-framework/milestones{/number}","notifications_url":"https://api.github.com/repos/europeana/metis-framework/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/europeana/metis-framework/labels{/name}","releases_url":"https://api.github.com/repos/europeana/metis-framework/releases{/id}","deployments_url":"https://api.github.com/repos/europeana/metis-framework/deployments","created_at":"2016-02-17T12:43:34Z","updated_at":"2018-03-26T13:43:29Z","pushed_at":"2018-03-28T12:50:30Z","git_url":"git://github.com/europeana/metis-framework.git","ssh_url":"git@github.com:europeana/metis-framework.git","clone_url":"https://github.com/europeana/metis-framework.git","svn_url":"https://github.com/europeana/metis-framework","homepage":"","size":127344,"stargazers_count":7,"watchers_count":7,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"archived":false,"open_issues_count":4,"license":{"key":"eupl-1.1","name":"European Union Public License 1.1","spdx_id":"EUPL-1.1","url":"https://api.github.com/licenses/eupl-1.1"},"forks":1,"open_issues":4,"watchers":7,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/europeana/metis-framework/pulls/136"},"html":{"href":"https://github.com/europeana/metis-framework/pull/136"},"issue":{"href":"https://api.github.com/repos/europeana/metis-framework/issues/136"},"comments":{"href":"https://api.github.com/repos/europeana/metis-framework/issues/136/comments"},"review_comments":{"href":"https://api.github.com/repos/europeana/metis-framework/pulls/136/comments"},"review_comment":{"href":"https://api.github.com/repos/europeana/metis-framework/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/europeana/metis-framework/pulls/136/commits"},"statuses":{"href":"https://api.github.com/repos/europeana/metis-framework/statuses/8fbf56a1135058a24c0d74e67ba835689ea5931d"}},"author_association":"COLLABORATOR"}}
{"actor":{"display_login":"stzanakis"}}