tag:blogger.com,1999:blog-41693753465270885122024-03-18T20:17:11.522-07:00Ramblings of a coderMichael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.comBlogger30125tag:blogger.com,1999:blog-4169375346527088512.post-67885028096630231692018-11-14T04:13:00.005-08:002019-01-21T02:42:26.086-08:00Building windows installers in a Linux CI environment using wine and innosetup<a href="https://github.com/quelea-projection">Quelea</a> is by far the "side project" that takes up the majority of my time. To aid with testing, I built in CI relatively early with a Jenkins server running on a custom VM. This was great - I could just push a change to the repo from anywhere, and then point the user to the CI release. They'd download it and be able to confirm whether the fix had worked (or not!)<br />
<br />
I've since switched to Travis and retired said VM (it's one less thing to maintain, and now everything is on Github.) But both these setups had one main issue - the windows installer wouldn't get built as part of this process, since they were Linux boxes and innosetup doesn't have a linux distribution. Travis has added windows support, but it's in early release, and in any case I'd like the entire build process to be able to run on any Linux box - it makes it both quicker and more transferrable if we ever need to move elsewhere.<br />
<br />
I therefore looked into using wine in the CI release to build the windows installer - and it's not too bad at all. There's a few non-obvious things to figure out though, so I'm describing the process here in case anyone else has the same requirement.<br />
<br />
I'm making some assumptions here - I'm not walking through creating the innosetup script (the iss file), nor am I walking through integrating innosetup with your build environment of choice. Before following this guide, you should have a build process that produces a working installer exe, from your build script, in a windows environment. We'll focus on translating this to a Linux environment so it can be built by any common CI tool (Travis, CircleCI, etc.)<br />
<br />
I'm using Travis and Gradle for the purposes of this walkthrough, but the concepts can be applied anywhere. So without further ado, you'll need to add the following to your .travis.yml:<br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">sudo: required</span><br />
<br />
(You'll need that.)<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">addons:</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> apt:</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> packages:</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> - wine</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> - xvfb</span><br />
<br />
You'll need to specify both wine and xvfb as packages for your CI environment to install. Wine is needed for obvious reasons. XVFB is the X virtual framebuffer, and is needed to emulate a graphical environment in a headless setting. (It can do all sorts of fancy things such as taking screenshots of the emulated framebuffer and simulating input events, but we don't need to worry about any of that.)<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">before_install:</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> - wget http://files.jrsoftware.org/is/5/innosetup-5.6.1.exe</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> - wineboot --update</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> - Xvfb :0 -screen 0 1024x768x16 &amp;</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> - DISPLAY=:0.0 wine innosetup-5.6.1.exe /VERYSILENT /SUPPRESSMSGBOXES</span><br />
<br />
Without "wineboot --update" you'll get some strange errors and very long hangs when the installer runs - that's the key to making the process go smoothly. The next line spins up the virtual framebuffer, and the line after executes the innosetup installer that wget downloaded in the first step. The final line installs innosetup into the wine environment, and from that point onwards it's available to your build environment!<br />
<br />
The next thing I'll create is a simple shell script to build the installer:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">#!/bin/bash</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">wine "/home/travis/.wine/drive_c/Program Files (x86)/Inno Setup 5/iscc.exe" /dMyAppVersion=$1 quelea64.iss /O /F /q"Quelea"</span><br />
<br />
...and then a Gradle task to call said shell script, and rename / move the output files when done:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">task innosetup (type:Exec) {</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> doFirst {</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> workingDir = project.file('.')</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) {</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> commandLine = ['cmd', '/C', 'build-install.bat', project.queleaversion]</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> } else {</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> commandLine = ['sh', './build-install.sh', project.queleaversion]</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> }</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> }</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> </span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> doLast {</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> copy {from file("Output/setup.exe") into file(project.distdir + "/standalone") rename { String filename -> return WindowsInstallerName}}</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> copy {from file("Output/setup64.exe") into file(project.distdir + "/standalone") rename { String filename -> return WindowsInstaller64Name}}</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> delete (file("Output"))</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> }</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">}</span><br />
<br />
Note here that I've still kept the windows batch file in place which enables me to build on either platform. Gradle will selectively call the correct script based on the platform.<br />
<br />
And that's pretty much it!<br />
<br />
If you'd like to see how we've integrated this process into Quelea, you can <a href="https://github.com/quelea-projection/Quelea/" target="_blank">poke around in the repo</a>. There's additional logic there to upload the CI artifacts to a Github release as well, which means I can always point people at a single Github release to grab the latest version (and don't have to maintain any off-platform infrastructure.)Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0tag:blogger.com,1999:blog-4169375346527088512.post-91004005400546242272017-12-16T05:37:00.000-08:002017-12-17T01:19:00.138-08:00An updated Dropbox API exampleBy far the most popular post on here (shock, I know!) was to do with <a href="http://berry120.blogspot.co.uk/2012/02/dropbox-java-api.html" target="_blank">using the Dropbox Java API from a desktop Java app</a>. At the initial time of writing, this wasn't really documented, so the OAuth flow in particular required a bit of guesswork to get it going.<br />
<br />
I haven't done any work with this API in quite a number of years now, and it appears the original v1 API was switched off a few months back, so that example will no longer work. This API has much better documentation than the v1 API did back in 2012, and if you're doing any serious work with it then looking through the <a href="https://github.com/dropbox/dropbox-sdk-java" target="_blank">SDK and the SDK examples on Github</a> is a must.<br />
<br />
That being said, for completeness I thought I'd provide an equivalent code snippet for the v2 API.<br />
<br />
You'll need the appropriate Maven or Gradle dependency:
<br />
<br />
<pre><dependency>
<groupid>com.dropbox.core</groupid>
<artifactid>dropbox-core-sdk</artifactid>
<version>3.0.5</version>
</dependency>
</pre>
<br />
or:
<br />
<br />
<pre>compile 'com.dropbox.core:dropbox-core-sdk:3.0.5'</pre>
<br />
<a href="https://github.com/dropbox/dropbox-sdk-java/releases/tag/v3.0.5" target="_blank">If you so wish, you can still download the library and include it manually.</a><br />
<br />
Then the following code snippet should just work similarly to before!
<br />
<br />
<pre>public class DropboxTest {
//Create an app and get these details from https://www.dropbox.com/developers/apps
private static final String APP_KEY = "APP KEY";
private static final String APP_SECRET = "APP SECRET";
public static void main(String[] args) throws Exception {
DbxRequestConfig config = new DbxRequestConfig("berry120_dropbox_example"); //Client name can be whatever you like
DbxAppInfo appInfo = new DbxAppInfo(APP_KEY, APP_SECRET);
DbxWebAuth webAuth = new DbxWebAuth(config, appInfo);
DbxWebAuth.Request webAuthRequest = DbxWebAuth.newRequestBuilder()
.withNoRedirect()
.build();
String url = webAuth.authorize(webAuthRequest);
Desktop.getDesktop().browse(new URL(url).toURI());
String code = JOptionPane.showInputDialog("Please click \"allow\" then enter your access code:");
DbxAuthFinish authFinish = webAuth.finishFromCode(code);
String accessToken = authFinish.getAccessToken(); //Store this for future use
DbxClientV2 client = new DbxClientV2(config, accessToken);
String fileContents = "Hello World!";
ByteArrayInputStream inputStream = new ByteArrayInputStream(fileContents.getBytes());
client.files().uploadBuilder("/testing.txt").uploadAndFinish(inputStream);
}
}
</pre>
<br />
<br />
As before, when you've obtained the accessToken (by calling <code>authFinish.getAccessToken()</code>), you can then just store this and use it directly.Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com5tag:blogger.com,1999:blog-4169375346527088512.post-66931192219893571302014-11-18T15:49:00.001-08:002015-01-06T16:06:15.539-08:00ZyXEL ES-1552 - removing ports from the default VLAN<i>Edit: As of January 2015 the ES-1552 has been discontinued.</i><br />
<br />
The ZyXEL ES-1552 is a 48 port 10/100 switch that's become horrifically good value recently, <strike>at the time of writing <a href="http://www.ebuyer.com/133444-zyxel-es-1552-48-port-10-100-web-managed-switch-with-2-x-es-1552" target="_blank">Ebuyer has it in stock for £27</a> (including delivery.)</strike> <i>(Sorry, it's now out of stock and discontinued.)</i> Granted, it's not gigabit, but still - a fully managed, fanless 48 port switch at that price? (And actually, it does have 2 gigabit ports included, so it's really a 50 port switch, and 52 if you include the two SFP slots. Anyway, I digress.)<br />
<br />
However, one of the downsides I keep seeing mentioned is that the web interface annoyingly doesn't let you remove ports from the default VLAN, even when the PVID of that port is set to a different VLAN! This would make it practically useless in a VLAN setting, but fortunately there's an easy workaround which I'll document here.<br />
<br />
I'll be using Chrome here, but any other browser with similar developer functionality should work just as well. For demo purposes, I'll be removing port 4 from the default VLAN. This may seem a bit long-winded, but it's really rather quick once you've done it for the first time.<br />
<br />
So without further ado:<br />
<br />
<br />
<ol>
<li>Fire up Chrome and plug in the switches' address, and log in.</li>
<li>Make sure you have at least one other VLAN - if you need to, create a second. (This is trivial, just click on "VLAN" in the left hand menu, click "Create new VLAN" and give it an id.)</li>
<li>Click on the "Port" link in the left hand menu, and check the port that you want to remove from the default VLAN has a PVID that's not 1:<br /><br /><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRX48VI383hT75t9x-7RJFNGukro3-E_h_5KbAECkkT66TL6tLGVXboBRX33ALWuhscTR7sOVgO4Ii39iNybTNngTf8ttOr89LNYy3fCQ_mce2d2QntjHy2V1WbiyqlNsOQr2zF_Y3KHLP/s1600/port.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRX48VI383hT75t9x-7RJFNGukro3-E_h_5KbAECkkT66TL6tLGVXboBRX33ALWuhscTR7sOVgO4Ii39iNybTNngTf8ttOr89LNYy3fCQ_mce2d2QntjHy2V1WbiyqlNsOQr2zF_Y3KHLP/s1600/port.png" height="154" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div style="text-align: left;">
<br />
In this case, port 4's PVID is 1, so we need to change it. Click on "04", change the PVID field to "2", and hit "Apply".</div>
</li>
<li>Click on the VLAN link in the left, you'll be greeted with something like this:<br /><br /><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgs2sKFSzXW5VhmkVNYo-WbiQVEiybB3wIQgi-e4TDkq4ZrhIa9iyPRHNTlcAXDxFcuCJqtXZMzGs1FstuVvXPr9RE_vUa-NJTXsR02cFZftZGTiRRUO9-_FZIFvl2jsa73AYdFQX1cukR7/s1600/vlan.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgs2sKFSzXW5VhmkVNYo-WbiQVEiybB3wIQgi-e4TDkq4ZrhIa9iyPRHNTlcAXDxFcuCJqtXZMzGs1FstuVvXPr9RE_vUa-NJTXsR02cFZftZGTiRRUO9-_FZIFvl2jsa73AYdFQX1cukR7/s1600/vlan.png" height="127" width="320" /></a></div>
</li>
<li><div class="separator" style="clear: both; text-align: left;">
<span style="font-weight: bold;">Right click a VLAN ID that is <i>not</i> 1 (so we right-click on 2 in our case), and select "Open link in new tab". Opening it in a new tab is important! You should see the VLAN table on its own in a tab (not with the normal left and top banner) like the following:</span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmLSCjU007Gy6doBGm0mIO1in2Tq0sQ0welPl189NycDlLFQGr4UV6ajQd51aNfTvjic4AFxjpa_fx2IEICkIp9nMkNgsHWjuCe5i7UHZrHQswZ2TUNNqaenpBUEe176-KCV_ytCndoFxX/s1600/vlan+2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmLSCjU007Gy6doBGm0mIO1in2Tq0sQ0welPl189NycDlLFQGr4UV6ajQd51aNfTvjic4AFxjpa_fx2IEICkIp9nMkNgsHWjuCe5i7UHZrHQswZ2TUNNqaenpBUEe176-KCV_ytCndoFxX/s1600/vlan+2.png" height="181" width="320" /></a></div>
<div style="text-align: center;">
<br /></div>
</li>
<li><div class="separator" style="clear: both; text-align: left;">
Click on the icon under the port you want to remove from VLAN 1 (port 04 in our case) until it's untagged</div>
</li>
<li><div class="separator" style="clear: both; text-align: left;">
Press ctrl+shift+i to open the Chrome developer toolbar.</div>
</li>
<li><div class="separator" style="clear: both; text-align: left;">
Hit the "console" button, then type in (exactly as here, no quotes) "cur_vid=1;" and press return:</div>
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEie9QXdKdwP_KvgFGtkgSmV1X1zhVyneAFI2UBJ16dK7KkgJsy5ZJqE2DEuNjQynYTM-ID6LLONOz2h82t9m09JgKhNVD8fkNaqIhKhb3dhawbtc66QwteqvFFPwTLvPyfmwwZuIGm1YGJP/s1600/dev.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEie9QXdKdwP_KvgFGtkgSmV1X1zhVyneAFI2UBJ16dK7KkgJsy5ZJqE2DEuNjQynYTM-ID6LLONOz2h82t9m09JgKhNVD8fkNaqIhKhb3dhawbtc66QwteqvFFPwTLvPyfmwwZuIGm1YGJP/s1600/dev.png" height="144" width="320" /></a><br />
</li>
<li><div class="separator" style="clear: both; text-align: left;">
Close the developer toolbar, then hit "Apply", and that should be it! Now when you click on "VLAN" in the left menu, then "01", you should see that the port has been removed:</div>
<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgh1Bdlmyz-BmEouLkIMIKi9Nt8nyX0GBaFQkkUF_Hrf5bssCMn9eDzFDZbeAeNLHthjlAbDcQRJXv12bg5v_PMfI63xrv0mWw4ruFcg0V_7bzD0A4W3thHW48hhJfBAwUkw6XL1JlawsZh/s1600/done.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgh1Bdlmyz-BmEouLkIMIKi9Nt8nyX0GBaFQkkUF_Hrf5bssCMn9eDzFDZbeAeNLHthjlAbDcQRJXv12bg5v_PMfI63xrv0mWw4ruFcg0V_7bzD0A4W3thHW48hhJfBAwUkw6XL1JlawsZh/s1600/done.png" height="140" width="320" /></a>.<br />
</li>
</ol>
<div>
<br /></div>
<div>
Any questions, comments or suggestions regarding the above process then do feel free to leave a comment. It's possible ZyXEL may fix this in an upcoming firmware update so ports can be removed from VLAN 1 without such a hack, but since the last firmware release was a number of years ago this does (unfortunately) seem unlikely.</div>
<div>
<br /></div>
<div>
Still, for a £27 switch I can live with firing up Chrome's developer toolbar and writing one line of Javascript when I need to perform a relatively rare operation!</div>
Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com4tag:blogger.com,1999:blog-4169375346527088512.post-23907902226709629552014-03-27T09:10:00.002-07:002014-03-27T09:18:37.529-07:00Expanding JavaFX's media support<i>Note: For those that don't want to read through the post and just want the patch for MKV support, you can grab it from <a href="https://javafx-jira.kenai.com/browse/RT-36398" target="_blank">this ticket</a>, or <a href="http://quelea.org/mkvfx.patch" target="_blank">here</a> if you don't have a JIRA account.</i><br />
<h2>
<span style="font-size: x-large;">
Background</span></h2>
One of the predominant things lacking a "nice" approach in the Java world for years now has been good media support. Oh sure, we had JMF, but anyone who ever had the misfortune of using that will I'm sure understand why that never really took on. (Yes, it really was that bad.) A few other approaches came and went, most notably <a href="https://weblogs.java.net/blog/2007/05/24/media-frenzy" target="_blank">Java Media Components</a> - but none ever made there way into core Java, and for a long time it became pretty de-facto knowledge that if you wanted any form of comprehensive media support in Java, you used a cross-platform native library, perhaps with a Java wrapper.<br />
<br />
However, when JavaFX 2 came along we were provided with a new, baked in <a href="http://docs.oracle.com/javafx/2/api/javafx/scene/media/package-summary.html#PlayingMediaInJavaFX" target="_blank">media framework</a> that provided this functionality on the Java level! This is a massive step forward, sure it uses GStreamer underneath but that's not really an issue - the required libraries are baked into JavaFX, and the end user can just treat the corresponding MediaView as any other node in the scenegraph.<br />
<br />
However, while this is much better than what we had previously, it's rather <a href="http://www.oracle.com/technetwork/java/javafx/overview/faq-1446554.html#7" target="_blank">limited support at this stage</a>:<br />
<br />
<h2 id="7" style="-webkit-font-smoothing: antialiased; background-color: white; font-family: arial, helvetica, sans-serif; font-size: 12px; list-style: none; margin: 0px; padding: 2px 0px 0px;">
<i>7. Does JavaFX provide support for audio and video codecs?</i></h2>
<span style="background-color: white; font-family: Arial, Helvetica, sans-serif; font-size: 12px;"><i>JavaFX provides a common set of APIs that make it easy to include media playback within any JavaFX application. The media formats currently supported are the following:</i></span><br />
<ul style="-webkit-font-smoothing: antialiased; background-color: white; font-family: arial, helvetica, sans-serif; font-size: 12px; list-style: none; margin: 0px 0px 12px; padding: 0px;">
<li style="-webkit-font-smoothing: antialiased; background-image: url(http://www.oracle.com/assets/css/list-icons.png); background-position: -266px -129px; background-repeat: no-repeat no-repeat; font-size: 12px; list-style: none none; margin: 4px 0px 10px; padding: 0px 0px 0px 26px;"><i>Audio: <span style="-webkit-font-smoothing: antialiased; color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 18px; list-style: none; margin: 0px; orphans: 2; padding: 0px; widows: 2;">MP3; AIFF containing uncompressed PCM; WAV containing uncompressed PCM; MPEG-4 multimedia container with Advanced Audio Coding (AAC) audio</span></i></li>
<li style="-webkit-font-smoothing: antialiased; background-image: url(http://www.oracle.com/assets/css/list-icons.png); background-position: -266px -129px; background-repeat: no-repeat no-repeat; font-size: 12px; list-style: none none; margin: 4px 0px 10px; padding: 0px 0px 0px 26px;"><i>Video: <span style="-webkit-font-smoothing: antialiased; color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 18px; list-style: none; margin: 0px; orphans: 2; padding: 0px; widows: 2;">FLV containing VP6 video and MP3 audio; MPEG-4 multimedia container with H.264/AVC (Advanced Video Coding) video compression</span> .</i></li>
</ul>
<div>
<span style="font-family: arial, helvetica, sans-serif;"><span style="font-size: 11.818181991577148px;"><br /></span></span></div>
<div>
The format support is far from useless; for videos bundled with an application that can be in any format, mp4 with h264 and AAC is certainly a relatively standard option. Likewise, if you're writing an application that uploads videos and uses something like ffmpeg to convert them over to a standard format before displaying them with a JFX frontend somehow, this is also adequate.</div>
<div>
<br /></div>
<div>
However, there are many use cases where the current support is very restrictive indeed - certainly any general purpose media player is out of the question, as is (realistically) any application where you want user-selected video files to play with any degree of reliability. Two of the most common container formats are out whatever formats are inside them (MKV and the badly ageing AVI), AC3 audio support isn't there... well, anything that isn't in the above list isn't there. Which is <i>a lot. </i>Many of these will have been excluded for licensing reasons, though even many free ones that could be in there aren't (MKV, OGG, FLAC, etc.)</div>
<div>
<br /></div>
<div>
But as said already, the JavaFX media classes use GStreamer to do the heavy lifting, which has all these formats (and more) available to it through plugins. So now that the whole thing is open sourced, it should in theory be possible to rebuild JFX with more GStreamer plugins compiled in, right? Turns out it is - the following is an outline of the process of how I did it. <b>I'll be adding support for the MKV container here</b>, other plugins can no doubt be added in a similar way.<br />
<br />
<i>Note: I'm far from an authority on this subject, I'm not a JFX developer and I'm certainly not advocating that what I describe here is necessarily all accurate, or correct. It's merely what I've been able to work out from tracing through the source and from some helpful people on the openjfx-dev mailing list.</i></div>
<h2>
<span style="font-size: x-large;">
Setting up the build</span></h2>
<div>
You'll firstly need to check out the JFX repo using Mercurial:</div>
<div>
<br /></div>
<div>
<pre>hg clone http://hg.openjdk.java.net/openjfx/8u-dev/master/</pre>
</div>
<div>
You will of course need a Mercurial client. If you're using Windows and haven't got a Mercurial client installed already, I highly recommend <a href="http://tortoisehg.bitbucket.org/" target="_blank">TortoiseHg</a>. The clone may take a while to complete (the repository is on the larger side!) so be patient.</div>
<div>
<br />
When you've checked out the repository, you'll then need to make sure you have all the prerequisites you need to build successfully - for that, see the <a href="https://wiki.openjdk.java.net/display/OpenJFX/Building+OpenJFX" target="_blank">build page</a> and make sure you have everything installed that you need (for Windows users, you <i>must</i> have Cygwin installed with the listed plugins.)<br />
<br />
There's a couple of points you also must take note of if you're running Windows that aren't mentioned in the document however:<br />
<br />
<ul>
<li>If the DirectX SDK fails to install then it's almost definitely because of the issue described <a href="http://stackoverflow.com/questions/4102259/directx-sdk-june-2010-installation-problems-error-code-s1023" target="_blank">here</a> - uninstall the relevant packages, then try the install again and it should go through without an issue.</li>
<li>You must also have the samples from the <a href="http://www.microsoft.com/en-gb/download/details.aspx?id=8279" target="_blank">Windows SDK</a> installed.</li>
<li>We want to compile the media module, which is disabled by default for timing reasons. In the root of the repo, copy <span style="background-color: white; color: #500050; font-family: arial, sans-serif; font-size: 13px;">gradle.properties.template<span style="color: black; font-family: 'Times New Roman'; font-size: small;"> to <span style="color: #500050; font-family: arial, sans-serif; font-size: 12.727272033691406px;">gradle.properties<span style="color: black; font-family: 'Times New Roman'; font-size: small;">,</span> </span>then uncomment the line (delete the first hash) that says "<span style="color: #500050; font-family: arial, sans-serif; font-size: 13px;">#COMPILE_MEDIA = true</span>".</span></span></li>
<li><span style="background-color: white; color: #500050; font-family: arial, sans-serif; font-size: 13px;"><span style="color: black; font-family: 'Times New Roman'; font-size: small;">Things will be much easier if you add your gradle bin folder to PATH.</span></span></li>
</ul>
<div>
You can then fire up a shell, type "gradle sdk" and watch it attempt to build. Since you haven't made any changes at this point, all should go well and you should be presented with a "BUILD SUCCESSFUL" message after a while (the build will take a few minutes to complete.) If not, then go back and double check you've set everything up as per the instructions on the build page. Of course, if you're still stuck then feel free to leave a comment :)</div>
<h2>
<span style="font-size: x-large;">
Making the changes</span></h2>
<div>
At this point, there's two ways you can proceed - the first is to just <a href="http://quelea.org/mkvfx.patch" target="_blank">grab the patch file</a>, apply it to the repo, and then rebuild (run "gradle sdk" again.) All being well, the MKV container will then be supported in the resulting build. So if you want to just do that the easy way, you can skip the rest of this step.</div>
<div>
<br /></div>
<div>
However, in the interest of being as informative as possible to those that want to repeat the step with another plugin, I'll describe the necessary changes here in detail. Changes on both the Java and native layer are required, so we'll start with the Java layer.<br />
<br />
<h3>
<span style="font-size: large;">
Java layer</span></h3>
</div>
<div>
The bit of the Java layer that we're interested in is really just responsible for performing some basic checks on the file's type to determine if it has a hope of playing it. This is a relatively simple process, hopefully explained by the diagram below:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiz49HAMEnU8Zf2k-hg2ccigk2yHGye7XZSmD87PCxZ8aBQEbIBGpzyT5fDD-pVcFmCJqfrY4t2G-E0UnYw-oPvTtOpEqO-rCU97DjFDF-S4l5afn0i7hLMdOv85boqrsh4qy9vTDQ0_zQo/s1600/jfx_media_java_layer.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiz49HAMEnU8Zf2k-hg2ccigk2yHGye7XZSmD87PCxZ8aBQEbIBGpzyT5fDD-pVcFmCJqfrY4t2G-E0UnYw-oPvTtOpEqO-rCU97DjFDF-S4l5afn0i7hLMdOv85boqrsh4qy9vTDQ0_zQo/s1600/jfx_media_java_layer.png" height="455" width="640" /></a></div>
<div>
<br /></div>
<div>
This means we have to add support in two places; we have to modify the filenameToContentType() method so it can work out the correct content type from the file name, and we then have to add it to the list of supported content types for the corresponding platform, GSTPlatform in this case (this is just an entry in the array.) Optionally we could also add knowledge of the signature to the fileSignatureToContentType() method, but that isn't strictly necessary since it's just used as a fallback if the type can't be worked out from the fileNameToContentType() method. In doing so you'll also need to add the extension and content type to the MediaUtils class:<br />
<br />
<pre style="white-space: pre-wrap; word-wrap: break-word;">public static final String CONTENT_TYPE_MKV = "video/x-matroska";</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;">private static final String FILE_TYPE_MKV = "mkv";</pre>
<br />
The file extension is obvious, but the content type should be grabbed from the GStreamer defined types list <a href="http://gstreamer.freedesktop.org/data/doc/gstreamer/head/pwg/html/section-types-definitions.html" target="_blank">here</a>.<br />
<br />
I won't go into huge amounts of detail on what exactly to change in the Java classes - it's pretty basic Java, and I'm assuming most of the people reading this will be Java programmers! You can of course look at the patch to see my exact changes. If you get stuck, feel free to leave a comment and I'll do my best to help.<br />
<br />
When you've done this, rebuild JFX (run "gradle sdk") and try creating a media object to point to an MKV file. If the above has worked successfully, you'll get a different (native) error to the one you got before. This is good - it means the Java layer is letting the file pass down to be played in the native layer - now we need to enable it here.<br />
<br />
<h3>
<span style="font-size: large;">
Native Layer</span></h3>
</div>
</div>
<div>
We now need to grab the required plugin for GStreamer - in the case of the matroska plugin, this is in the "plugins-good" category, which means it's a well written and tested plugin that shouldn't pose distribution problems. The tarball of source for the plugins can be grabbed from <a href="http://gstreamer.freedesktop.org/src/gst-plugins-good/" target="_blank">here</a>.</div>
<div>
<br /></div>
<div>
However, make sure when you're doing this that you grab the correct version of the plugins - JavaFX (for 8u20 and before at least) isn't built with the latest GStreamer. To find out what one, we can look at the <a href="http://www.oracle.com/technetwork/opensource/gstreamer-1668149.html" target="_blank">GStreamer modifications</a> that Oracle publish, a zip file containing modifications to the bits of GStreamer they've included. This shows the plugins being used are the 0.10.30 ones (for the good branch anyway), so go ahead, download the <a href="http://gstreamer.freedesktop.org/src/gst-plugins-good/gst-plugins-good-0.10.30.tar.gz" target="_blank">0.10.30 good plugins</a> and pull out the matroska one, and drop it in the relevant directory - in this case "<span style="white-space: pre-wrap;">modules/media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/".</span></div>
<div>
<span style="white-space: pre-wrap;"><br /></span></div>
<div>
<span style="white-space: pre-wrap;">Most of the modifications that Oracle make are fixes and performance improvements to the other plugins, though one required change is to the plugin loading system. In the main plugin C file, matroska.c in this case, you'll see an init function like the following:</span></div>
<div>
<span style="white-space: pre-wrap;"><br /></span></div>
<div>
<pre style="white-space: pre-wrap; word-wrap: break-word;">GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
"matroska",
"Matroska and WebM stream handling",
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: 'Times New Roman';">We don't need this function, so we can remove it - instead of removing it though, make sure you follow the convention of wrapping it in "#ifndef":</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: 'Times New Roman';">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;">+#ifndef GSTREAMER_LITE
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ "matroska",
+ "Matroska and WebM stream handling",
+ plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
+#endif</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">This then makes it much easier to find where changes have been made later on, as well as preserving the original functionality if it's not being built in the JFX environment for whatever reason (in which case GSTREAMER_LITE won't be defined, so the above will execute.)</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">We also need to change the plugin_init function in a similar way, which is the following:</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;">static gboolean
plugin_init (GstPlugin * plugin)</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">We want to make two changes here - we don't want it to be static, and we want the name to be more unique so it can be initialised alongside other plugins without any conflict. The convention appears to be "plugin_init_pluginname", so replace the above with the following:</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;">#ifdef GSTREAMER_LITE
gboolean
plugin_init_matroska (GstPlugin * plugin)
#else // GSTREAMER_LITE
static gboolean
plugin_init (GstPlugin * plugin)
#endif // GSTREAMER_LITE</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">Again, the changes are wrapped in the appropriate tags to make it clear what we've changed.</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">That's all the changes for this file, but we need to add the method we've defined (plugin_init_matroska in this case) to the appropriate headers file, "</span>modules/media/src/main/native/gstreamer/gstreamer-lite/projects/plugins/gstplugins-lite.h". <span style="font-family: Times New Roman;">Open it, and add:</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;">gboolean plugin_init_matroska (GstPlugin * plugin);</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">...or whatever you called your function above to the list of method headers. We also need to make sure the function is called to initialise the plugin, so open up </span>modules/media/src/main/native/gstreamer/gstreamer-lite/projects/plugins/gstplugins-lite.c <span style="font-family: Times New Roman;">and find the section where the plugins are initialised, it'll be a list in a big if statement looking something like this:</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">...</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;">!plugin_init_aiff(plugin) ||
!plugin_init_app(plugin) ||
!plugin_init_audioparsers(plugin) ||</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">...</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">Then just add the call to your plugin in the same way:</span></pre>
<pre style="word-wrap: break-word;"><pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">...</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;">!plugin_init_aiff(plugin) ||
!plugin_init_app(plugin) ||
!plugin_init_audioparsers(plugin) ||</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;">!plugin_init_matroska(plugin) ||</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">...</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">We now need to update the JavaFX cpp bridging code to make it aware of the format, and create and return a pipeline for it. Let's start by defining the format in </span>modules/media/src/main/native/jfxmedia/MediaManagement/MediaTypes.h <span style="font-family: Times New Roman;">- this is just a case of adding it to the list, making sure it's the same as you defined it in the Java code. So for this case, the following needs to be added:</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;">#define CONTENT_TYPE_MKV "video/x-matroska"</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: 'Times New Roman';">Next, open </span>modules/media/src/main/native/jfxmedia/platform/gstreamer/GstPipelineFactory.cpp <span style="font-family: 'Times New Roman';">- this is where the pipeline creation actually takes place. There's a few things that need to be changed in here:</span></pre>
<pre style="word-wrap: break-word;"><ol>
<li><span style="font-family: Times New Roman;"><span style="white-space: pre-wrap;">Find the pushback function calls, something like:
<pre style="white-space: pre-wrap; word-wrap: break-word;"> m_ContentTypes.push_back(CONTENT_TYPE_MP4);
m_ContentTypes.push_back(CONTENT_TYPE_M4A);
m_ContentTypes.push_back(CONTENT_TYPE_M4V);</pre>
Then add your content type to it in the same way:
<pre style="white-space: pre-wrap; word-wrap: break-word;"> m_ContentTypes.push_back(CONTENT_TYPE_MKV);</pre>
</span></span></li>
<li><pre style="white-space: pre-wrap; word-wrap: break-word;"><pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: 'Times New Roman';">The next step is a bit less clear cut, you need to find the </span><span style="background-color: white; color: #333333; font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', Monaco, monospace; font-size: 12px; line-height: 1.4000000000000001;">CreatePlayerPipeline </span><span style="font-family: 'Times New Roman';">function, and identify the part where your pipeline should be created. You'll see a general pattern here - the video container formats are dealt with within one if statement that sets up a video sink before creating the pipeline, and the audio formats are dealt with afterwards. So in the appropriate place, you need to follow the pattern to hook in and call a method to create your pipeline. For MKV, it seemed to make most sense to add it to the if/else block just after checking for mp4 files, so straight afterwards I added this:
<pre style="white-space: pre-wrap; word-wrap: break-word;"> else if (CONTENT_TYPE_MKV == locator->GetContentType())
{
uRetCode = CreateMKVPipeline(pSource, pVideoSink, (CPipelineOptions*) pOptions, ppPipeline);
if (ERROR_NONE != uRetCode)
return uRetCode;
}</pre>
</span></pre>
</pre>
</li>
<li><pre style="white-space: pre-wrap; word-wrap: break-word;"><pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: 'Times New Roman';">Of course, you then need to write the function to return the pipeline, and again you can follow the pattern of similar ones for this. Once again, MKV is similar in the way it's handled to MP4 so I simply copied that function and made the appropriate changes thus producing:<ol style="font-family: monospace; white-space: pre;">
<li><pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: 'Times New Roman';"><pre style="white-space: pre-wrap; word-wrap: break-word;">uint32_t CGstPipelineFactory::CreateMKVPipeline(GstElement* source, GstElement* pVideoSink, CPipelineOptions* pOptions, CPipeline** ppPipeline)
{
#if TARGET_OS_WIN32
return CreateAVPipeline(source, "matroskademux", "dshowwrapper", true, dshowwrapper", pVideoSink, pOptions, ppPipeline);
#elif TARGET_OS_MAC
return CreateAVPipeline(source, "matroskademux", "audioconverter", false, avcdecoder", pVideoSink, pOptions, ppPipeline);
#elif TARGET_OS_LINUX
#if ENABLE_GST_FFMPEG
return CreateAVPipeline(source, "matroskademux", "ffdec_aac", true,
"ffdec_h264", pVideoSink, pOptions, ppPipeline);
#else // ENABLE_GST_FFMPEG
return CreateAVPipeline(source, "matroskademux", "avaudiodecoder", false, "avvideodecoder", pVideoSink, pOptions, ppPipeline);
#endif // ENABLE_GST_FFMPEG
#else
return ERROR_PLATFORM_UNSUPPORTED;
#endif // TARGET_OS_WIN32
}</pre>
</span></pre>
</li>
</ol>
The only things I changed from the CreateMP4Pipeline were the name of the function, and the name of the demuxing plugin (matroskademux in this case) - everything else remains the same.</span></pre>
</pre>
</li>
<li><pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">Of course, you'll now need to add the above function to the relevant header file, so in </span>modules/media/src/main/native/jfxmedia/platform/gstreamer/GstPipelineFactory.h<span style="font-family: Times New Roman;">, add:
<pre style="white-space: pre-wrap; word-wrap: break-word;">uint32_t CreateMKVPipeline(GstElement* source, GstElement* videosink, CPipelineOptions* pOptions, CPipeline** ppPipeline);
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">...to the list of functions.</span></pre>
</pre>
</span></pre>
</li>
</ol>
<div>
<span style="font-family: Times New Roman;"><span style="white-space: pre-wrap;">That should be all the changes you need to make to the native code, now we just need to ensure it's compiled and linked properly. So to start with, you'll need to update the plugins makefile, </span></span><span style="white-space: pre-wrap;">modules/media/src/main/native/gstreamer/projects/win/gstreamer-lite/Makefile.gstplugins<span style="font-family: Times New Roman;"> - add the directory and all the c files in that appropriate directory. So my list of directories now looks (partly) something like:</span></span></div>
<div>
<span style="white-space: pre-wrap;"><span style="font-family: Times New Roman;">...</span></span></div>
<div>
<pre style="white-space: pre-wrap; word-wrap: break-word;">gst-plugins-good/gst/spectrum/ \
gst-plugins-good/gst/wavparse/ \
<b>gst-plugins-good/gst/matroska/ \</b>
gstreamer/plugins/elements/ \
gstreamer/plugins/indexers/ \</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: 'Times New Roman';">...</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: 'Times New Roman';">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: 'Times New Roman';">And the list of files:</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: 'Times New Roman';">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: 'Times New Roman';">...</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;">gst-plugins-good/gst/spectrum/gstspectrum.c \
gst-plugins-good/gst/wavparse/gstwavparse.c \
<b>gst-plugins-good/gst/matroska/webm-mux.c \
gst-plugins-good/gst/matroska/matroska-parse.c \
gst-plugins-good/gst/matroska/matroska-mux.c \
gst-plugins-good/gst/matroska/matroska-ids.c \
gst-plugins-good/gst/matroska/matroska-demux.c \
gst-plugins-good/gst/matroska/matroska.c \
gst-plugins-good/gst/matroska/lzo.c \
gst-plugins-good/gst/matroska/ebml-write.c \
gst-plugins-good/gst/matroska/ebml-read.c \</b>
gstreamer/plugins/elements/gstcapsfilter.c \
gstreamer/plugins/elements/gstelements.c \</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: 'Times New Roman';">...</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: 'Times New Roman';">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">The boldings are my addition (obviously the above is just an excerpt.)</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">Now you can try and build and see if you encounter any compilation errors (you could and arguably should of course, do this as you're going through as well.) I found odd things sometimes cropped up if I didn't do a clean (gradle clean) before re-building, so if something doesn't seem quite right, bear that in mind.</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">You shouldn't have any compilation errors at this point, but you almost certainly will have linker errors (probably starting with "Unresolved external symbol" or something similar, then referencing a function.) </span><span style="font-family: 'Times New Roman';">These functions will either start with g_ or gst_ (ignore the leading underscore.) The ones that start with gst need to be added to </span>modules/media/src/main/native/gstreamer/projects/win/gstreamer-lite.def <span style="font-family: Times New Roman;">matching the format of the file already there. This means for each function, you need a new line in the format of:</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;">function_name<TAB>@nextsequentialnumber<TAB>NONAME.</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">Obviously, function_name should be replaced with the function name, <TAB> should be replaced with an actual tab and nextsequentialnumber should be replaced with, you guessed it, the next sequential number. Apart from that you don't need to worry about ordering. For the matroska plugin, I had to add the following to the end of the file:</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;">gst_byte_writer_free_and_get_buffer @184 NONAME
gst_byte_writer_free @185 NONAME
gst_byte_writer_new_with_size @186 NONAME</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">For the functions that start with "g_" instead, you need to add these in the exact same way to <i>both</i> the </span>modules/media/src/main/native/gstreamer/3rd_party/glib/glib-2.28.8/build/win32/vs100/glib-lite.def <span style="font-family: 'Times New Roman';">and the </span>modules/media/src/main/native/gstreamer/3rd_party/glib/glib-2.28.8/build/win32/vs100/glib-liteD.def <span style="font-family: Times New Roman;">files.</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">If you recompile and it now complains about an unresolved external in one of the def files itself, changes are the source that contains those functions isn't actually getting compiled (because it wasn't needed in any of the plugins already there.) For the Matroska plugin this is the case for the bytewriter functions mentioned above. These are in gstbytewriter.c, and a quick check in the makefile (</span>modules/media/src/main/native/gstreamer/projects/win/gstreamer-lite/Makefile.gstreamer<span style="font-family: Times New Roman;">) indeed confirmed that it wasn't on the list, so that was promptly added:</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;">gstreamer/libs/gst/base/gstbasetransform.c \
gstreamer/libs/gst/base/gstbytereader.c \
<b>gstreamer/libs/gst/base/gstbytewriter.c \</b>
gstreamer/libs/gst/base/gstcollectpads.c \
gstreamer/libs/gst/base/gstpushsrc.c \</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">And that's it - after performing those steps, doing a full build, then running against the built dll's and jfxrt.jar, media support for MKV (or whatever other format you've chosen) should just work!</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"></pre>
<h2 style="white-space: pre-wrap; word-wrap: break-word;">
<span style="font-family: Times New Roman; font-size: x-large;">Summary</span></h2>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">These are relatively extensive instructions, but none are really major changes. Summarised, they're pretty much as follows:</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="font-family: Times New Roman;">
</span></pre>
<pre style="word-wrap: break-word;"><ol>
<li><span style="font-family: Times New Roman;"><span style="white-space: pre-wrap;">Edit the fileNameToContentType() method to return the correct content type for your file name.</span></span></li>
<li><span style="font-family: Times New Roman;"><span style="white-space: pre-wrap;">Optionally edit the fileSignatureToContentType() method to return the correct type for your file signature (<a href="http://www.garykessler.net/library/file_sigs.html" target="_blank">this page</a> may be of use here.)</span></span></li>
<li><span style="font-family: Times New Roman;"><span style="white-space: pre-wrap;">Add the content type to the list of GSTPlatform's supported content types</span></span></li>
<li><span style="font-family: Times New Roman;"><span style="white-space: pre-wrap;">Download the plugin file and drop it in the relevant directory</span></span></li>
<li><span style="font-family: Times New Roman;"><span style="white-space: pre-wrap;">In the plugin's main file, rename the plugin_init function to something more unique, remove the static modifier</span></span></li>
<li><span style="font-family: Times New Roman;"><span style="white-space: pre-wrap;">Initialise the plugin in gstplugins-lite.c</span></span></li>
<li><span style="font-family: Times New Roman;"><span style="white-space: pre-wrap;">Define the media type in MediaTypes.h</span></span></li>
<li><span style="font-family: Times New Roman;"><span style="white-space: pre-wrap;">In GSTPipelineFactory.cpp, call m_ContentTypes.push_back on the content type</span></span></li>
<li><span style="font-family: Times New Roman;"><span style="white-space: pre-wrap;">In the same file, in the CreatePlayerPipeline function, add in a hook to call a function to create the pipeline for your content type</span></span></li>
<li><span style="font-family: Times New Roman;"><span style="white-space: pre-wrap;">Create the above function (using the other ones in that file as a template)</span></span></li>
<li><span style="font-family: Times New Roman;"><span style="white-space: pre-wrap;">Add the relevant files / directory to Makefile.gstplugins</span></span></li>
<li><span style="font-family: Times New Roman;"><span style="white-space: pre-wrap;">Make sure all the files you rely on are being built, add any that aren't to Makefile.gstreamer</span></span></li>
<li><span style="font-family: Times New Roman;"><span style="white-space: pre-wrap;">Add any gstreamer functions (gst) to gstreamer-lite.def</span></span></li>
<li><span style="font-family: Times New Roman;"><span style="white-space: pre-wrap;">Add any glib functions (g) to glib-lite.def and glib-liteD.def</span></span></li>
</ol>
<div>
<span style="font-family: Times New Roman;"><span style="white-space: pre-wrap;">As said already, the above guide is my (little) experience simply with playing around and adding MKV support, so I can't guarantee it will be exactly the same for other plugins and formats. However, it should at least serve as a starting guide for those wanting to build much more comprehensive media support into JavaFX. There's many, many gstreamer plugins available, most of which will, in all likelihood never be included in JavaFX core because of licensing issues. But if you want to build your own version of JFX to distribute with your application, then building much more media support into it, as described above, is more than do-able.</span></span></div>
</pre>
</div>
</pre>
</pre>
</div>
Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com11tag:blogger.com,1999:blog-4169375346527088512.post-58902883068568652862014-01-31T17:35:00.000-08:002014-02-01T16:52:58.594-08:00Draggable and detachable tabs in JavaFX 2JavaFX currently doesn't have the built in ability to change the order of tabs by dragging them, neither does it have the ability to detach tabs into separate windows (like a lot of browsers do these days.) There is a general issue for improving TabPanes filed <a href="https://javafx-jira.kenai.com/browse/RT-19659">here</a>, so if you'd like to see this sort of behaviour added in the main JavaFX libraries then go ahead and cast your vote, it would be a very welcome addition!<br />
<br />
However, as nice as this would be in the future, it's not here at the moment and it looks highly unlikely it'll be here for Java 8 either. I've seen a few brief attempts at reordering tabs in JavaFX, but very few examples on dragging them and nothing to do with detaching / reattaching them from the pane.
<br />
<br />
Given this, I've decided to create a reusable class that should hopefully be as easy as possible to integrate into existing applciations - it extends from Tab, and for the most part you create it and use it like a normal tab (you can just add it to a normal TabPane for instance.) It works pretty well for me as you can see in this simple example:
<br/>
<br/>
<iframe width="420" height="315" src="//www.youtube.com/embed/ALYtkXrzHDI" frameborder="0" allowfullscreen></iframe>
<br/><br/>
There's a few things to be aware of however before you rush out and use it!
<br />
<ul>
<li>To set the text, make sure you use setLabelText() rather than setText(), otherwise you'll get odd results. Sadly the latter is final so I can't override it.</li>
<li>You can't have DraggableTabs and normal Tabs on the same TabPane, otherwise you'll see all sorts of errors pop up. If you don't want a particular tab to be detachable, just call setDetachable(false). Tab 1 (the black tab) is set this way in the example program below.</li>
<li>This seems to work well for me, but it's far from bulletproof - use at your own risk! It should be pretty easy to work out what's going on though so if you want to change it, modify or otherwise extend it then redistribute it, feel free.</li>
</ul>
<br/>
<br/>
<b>If you don't want to copy / paste from here, just grab the <a href="http://quelea.org/fxtabs.zip">raw files</a>.</b>
<br/>
<br/>
<pre class="brush: java; gutter: false;">
/**
* Just a very simple sample application that uses the class below.
*/
public class FXTabs extends Application {
@Override
public void start(final Stage primaryStage) {
DraggableTab tab1 = new DraggableTab("Tab 1");
tab1.setClosable(false);
tab1.setDetachable(false);
tab1.setContent(new Rectangle(500, 500, Color.BLACK));
DraggableTab tab2 = new DraggableTab("Tab 2");
tab2.setClosable(false);
tab2.setContent(new Rectangle(500, 500, Color.RED));
DraggableTab tab3 = new DraggableTab("Tab 3");
tab3.setClosable(false);
tab3.setContent(new Rectangle(500, 500, Color.BLUE));
DraggableTab tab4 = new DraggableTab("Tab 4");
tab4.setClosable(false);
tab4.setContent(new Rectangle(500, 500, Color.ORANGE));
TabPane tabs = new TabPane();
tabs.getTabs().add(tab1);
tabs.getTabs().add(tab2);
tabs.getTabs().add(tab3);
tabs.getTabs().add(tab4);
StackPane root = new StackPane();
root.getChildren().add(tabs);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
}
</pre>
<br/>
<br/>
All the real work happens in this class - it's not the neatest thing in the world by a long stretch, but I've kept it all in one place to make it easier to just copy across and experiment with:
<br />
<br />
<pre class="brush: java; gutter: false;">
import java.util.HashSet;
import java.util.Set;
import javafx.collections.ListChangeListener;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.WindowEvent;
/**
* A draggable tab that can optionally be detached from its tab pane and shown
* in a separate window. This can be added to any normal TabPane, however a
* TabPane with draggable tabs must *only* have DraggableTabs, normal tabs and
* DrragableTabs mixed will cause issues!
* <p>
* @author Michael Berry
*/
public class DraggableTab extends Tab {
private static final Set<TabPane> tabPanes = new HashSet<>();
private Label nameLabel;
private Text dragText;
private static final Stage markerStage;
private Stage dragStage;
private boolean detachable;
static {
markerStage = new Stage();
markerStage.initStyle(StageStyle.UNDECORATED);
Rectangle dummy = new Rectangle(3, 10, Color.web("#555555"));
StackPane markerStack = new StackPane();
markerStack.getChildren().add(dummy);
markerStage.setScene(new Scene(markerStack));
}
/**
* Create a new draggable tab. This can be added to any normal TabPane,
* however a TabPane with draggable tabs must *only* have DraggableTabs,
* normal tabs and DrragableTabs mixed will cause issues!
* <p>
* @param text the text to appear on the tag label.
*/
public DraggableTab(String text) {
nameLabel = new Label(text);
setGraphic(nameLabel);
detachable = true;
dragStage = new Stage();
dragStage.initStyle(StageStyle.UNDECORATED);
StackPane dragStagePane = new StackPane();
dragStagePane.setStyle("-fx-background-color:#DDDDDD;");
dragText = new Text(text);
StackPane.setAlignment(dragText, Pos.CENTER);
dragStagePane.getChildren().add(dragText);
dragStage.setScene(new Scene(dragStagePane));
nameLabel.setOnMouseDragged(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent t) {
dragStage.setWidth(nameLabel.getWidth() + 10);
dragStage.setHeight(nameLabel.getHeight() + 10);
dragStage.setX(t.getScreenX());
dragStage.setY(t.getScreenY());
dragStage.show();
Point2D screenPoint = new Point2D(t.getScreenX(), t.getScreenY());
tabPanes.add(getTabPane());
InsertData data = getInsertData(screenPoint);
if(data == null || data.getInsertPane().getTabs().isEmpty()) {
markerStage.hide();
}
else {
int index = data.getIndex();
boolean end = false;
if(index == data.getInsertPane().getTabs().size()) {
end = true;
index--;
}
Rectangle2D rect = getAbsoluteRect(data.getInsertPane().getTabs().get(index));
if(end) {
markerStage.setX(rect.getMaxX() + 13);
}
else {
markerStage.setX(rect.getMinX());
}
markerStage.setY(rect.getMaxY() + 10);
markerStage.show();
}
}
});
nameLabel.setOnMouseReleased(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent t) {
markerStage.hide();
dragStage.hide();
if(!t.isStillSincePress()) {
Point2D screenPoint = new Point2D(t.getScreenX(), t.getScreenY());
TabPane oldTabPane = getTabPane();
int oldIndex = oldTabPane.getTabs().indexOf(DraggableTab.this);
tabPanes.add(oldTabPane);
InsertData insertData = getInsertData(screenPoint);
if(insertData != null) {
int addIndex = insertData.getIndex();
if(oldTabPane == insertData.getInsertPane() && oldTabPane.getTabs().size() == 1) {
return;
}
oldTabPane.getTabs().remove(DraggableTab.this);
if(oldIndex < addIndex && oldTabPane == insertData.getInsertPane()) {
addIndex--;
}
if(addIndex > insertData.getInsertPane().getTabs().size()) {
addIndex = insertData.getInsertPane().getTabs().size();
}
insertData.getInsertPane().getTabs().add(addIndex, DraggableTab.this);
insertData.getInsertPane().selectionModelProperty().get().select(addIndex);
return;
}
if(!detachable) {
return;
}
final Stage newStage = new Stage();
final TabPane pane = new TabPane();
tabPanes.add(pane);
newStage.setOnHiding(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent t) {
tabPanes.remove(pane);
}
});
getTabPane().getTabs().remove(DraggableTab.this);
pane.getTabs().add(DraggableTab.this);
pane.getTabs().addListener(new ListChangeListener<Tab>() {
@Override
public void onChanged(ListChangeListener.Change<? extends Tab> change) {
if(pane.getTabs().isEmpty()) {
newStage.hide();
}
}
});
newStage.setScene(new Scene(pane));
newStage.initStyle(StageStyle.UTILITY);
newStage.setX(t.getScreenX());
newStage.setY(t.getScreenY());
newStage.show();
pane.requestLayout();
pane.requestFocus();
}
}
});
}
/**
* Set whether it's possible to detach the tab from its pane and move it to
* another pane or another window. Defaults to true.
* <p>
* @param detachable true if the tab should be detachable, false otherwise.
*/
public void setDetachable(boolean detachable) {
this.detachable = detachable;
}
/**
* Set the label text on this draggable tab. This must be used instead of
* setText() to set the label, otherwise weird side effects will result!
* <p>
* @param text the label text for this tab.
*/
public void setLabelText(String text) {
nameLabel.setText(text);
dragText.setText(text);
}
private InsertData getInsertData(Point2D screenPoint) {
for(TabPane tabPane : tabPanes) {
Rectangle2D tabAbsolute = getAbsoluteRect(tabPane);
if(tabAbsolute.contains(screenPoint)) {
int tabInsertIndex = 0;
if(!tabPane.getTabs().isEmpty()) {
Rectangle2D firstTabRect = getAbsoluteRect(tabPane.getTabs().get(0));
if(firstTabRect.getMaxY()+60 < screenPoint.getY() || firstTabRect.getMinY() > screenPoint.getY()) {
return null;
}
Rectangle2D lastTabRect = getAbsoluteRect(tabPane.getTabs().get(tabPane.getTabs().size() - 1));
if(screenPoint.getX() < (firstTabRect.getMinX() + firstTabRect.getWidth() / 2)) {
tabInsertIndex = 0;
}
else if(screenPoint.getX() > (lastTabRect.getMaxX() - lastTabRect.getWidth() / 2)) {
tabInsertIndex = tabPane.getTabs().size();
}
else {
for(int i = 0; i < tabPane.getTabs().size() - 1; i++) {
Tab leftTab = tabPane.getTabs().get(i);
Tab rightTab = tabPane.getTabs().get(i + 1);
if(leftTab instanceof DraggableTab && rightTab instanceof DraggableTab) {
Rectangle2D leftTabRect = getAbsoluteRect(leftTab);
Rectangle2D rightTabRect = getAbsoluteRect(rightTab);
if(betweenX(leftTabRect, rightTabRect, screenPoint.getX())) {
tabInsertIndex = i + 1;
break;
}
}
}
}
}
return new InsertData(tabInsertIndex, tabPane);
}
}
return null;
}
private Rectangle2D getAbsoluteRect(Control node) {
return new Rectangle2D(node.localToScene(node.getLayoutBounds().getMinX(), node.getLayoutBounds().getMinY()).getX() + node.getScene().getWindow().getX(),
node.localToScene(node.getLayoutBounds().getMinX(), node.getLayoutBounds().getMinY()).getY() + node.getScene().getWindow().getY(),
node.getWidth(),
node.getHeight());
}
private Rectangle2D getAbsoluteRect(Tab tab) {
Control node = ((DraggableTab) tab).getLabel();
return getAbsoluteRect(node);
}
private Label getLabel() {
return nameLabel;
}
private boolean betweenX(Rectangle2D r1, Rectangle2D r2, double xPoint) {
double lowerBound = r1.getMinX() + r1.getWidth() / 2;
double upperBound = r2.getMaxX() - r2.getWidth() / 2;
return xPoint >= lowerBound && xPoint <= upperBound;
}
private static class InsertData {
private final int index;
private final TabPane insertPane;
public InsertData(int index, TabPane insertPane) {
this.index = index;
this.insertPane = insertPane;
}
public int getIndex() {
return index;
}
public TabPane getInsertPane() {
return insertPane;
}
}
}
</pre>Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com7tag:blogger.com,1999:blog-4169375346527088512.post-1936632028048373022013-04-13T03:51:00.001-07:002016-03-22T04:16:45.396-07:00The comprehensive (and free) DVD / Blu-ray ripping Guide!<span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><b>Note: If you've read this guide already (or when you've read it) then going through all of it each time you want to rip something can be a bit of a pain, especially when you just need your memory jogging on one particular section. Because of that, I've put together a quick "cheat sheet" <a href="http://quelea.org/RippingCheatSheet.pdf" target="_blank">here</a> which acts as a handy reference just to jog your memory on each key step.</b></span><br />
<span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span>
<span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">I've seen a few guides around on ripping DVDs, but fewer for Blu-rays, and many miss what I believe are important steps (such as ensuring the correct foreign language subtitles are preserved!) While ripping your entire DVD collection would have seemed insane due to storage requirements even a few years ago, these days it can make perfect sense.</span><br />
<span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">This guide doesn't show you a one click approach that does all the work for you, it's much more of a manual process. But the benefits of putting a bit more effort in really do pay off - you get to use entirely free tools with no demo versions, it's reliable and works with all discs I've tried it with (bar one that was heavily scratched!), you get much more control over the quality / file size ratio and quality / encode time ratio, you can ensure any forced subtitles are correctly transferred across - the list goes on. You can also be sure that you get a high quality, completely DRM-free rip that you can do with as you choose without fear that some built in time bomb is going to stop the file from ever playing again if you transfer it to another computer.</span></span><br />
<span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span>
<span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><b>Usual disclaimer that this isn't necessarily legal in your country, so check first and proceed with caution - I present this as a technical guide on the process only. If you are going to do it, just make a single personal copy.</b></span><br />
<br />
<h3>
Tools</h3>
<span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">I'll detail the tools used as we go, but it may make sense to install them ahead of time so you can follow the article more easily.</span><br />
<br style="color: #222222; font-family: Arial, Verdana, sans-serif; font-size: 12px;" />
<a data-cke-saved-href="http://www.makemkv.com/download/" href="http://www.makemkv.com/download/" rel="nofollow" style="font-family: Arial, Verdana, sans-serif; font-size: 12px;">MakeMKV</a><span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"> - We'll be using this to rip the raw files from the disc, this works with both Blu-rays and DVDs. If MakeMKV complains about not having a license key, grab the latest one from </span><a data-cke-saved-href="http://www.makemkv.com/forum2/viewtopic.php?f=5&t=1053" href="http://www.makemkv.com/forum2/viewtopic.php?f=5&t=1053" rel="nofollow" style="font-family: Arial, Verdana, sans-serif; font-size: 12px;">here</a><span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">.</span><br />
<a data-cke-saved-href="http://handbrake.fr/downloads.php" href="http://handbrake.fr/downloads.php" rel="nofollow" style="font-family: Arial, Verdana, sans-serif; font-size: 12px;">Handbrake </a><span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">- We'll be using this to compress the file that MKV generates down to a more reasonable size.</span><br />
<div>
<span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><a href="http://www.videolan.org/vlc/" target="_blank">VLC </a>- We'll be using this to play / check the MKV files. Any media player that can play them will do, but if you're not sure grab this since it definitely does.</span></div>
<div>
<br /></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><a href="http://sourceforge.net/projects/mkvextractgui-2/" target="_blank">MKVExtract </a>- We'll be using this to extract subtitle streams.</span></span><br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><a href="https://code.google.com/p/subtitleedit/downloads/list?can=3&q=&colspec=Filename+Summary+Uploaded+ReleaseDate+Size+DownloadCount" target="_blank">Subtitle Edit</a> - </span></span><span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">We'll be using this to convert subtitles into a better format.</span><br />
<span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span>
<br />
<h3>
The Initial Rip</h3>
<span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">Put your disc in the drive and load up MakeMKV (get rip of any auto-playing DVD players that might pop up.) You should be presented with something like the following:</span><br />
<span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi67pkd1TnCUq87rj0_pnJIKwIPDFJX1EU5SGo-xhbbzxWipcTpRnSWv8dsgrc3r9FPD12C14ZTQgraShQZF6y6Ia_dYk4lRZEV82x5i1SaErqjJVo1Ilx73rVOUTHxp9tHZjOILazznXlb/s1600/makemkv+initial.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="221" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi67pkd1TnCUq87rj0_pnJIKwIPDFJX1EU5SGo-xhbbzxWipcTpRnSWv8dsgrc3r9FPD12C14ZTQgraShQZF6y6Ia_dYk4lRZEV82x5i1SaErqjJVo1Ilx73rVOUTHxp9tHZjOILazznXlb/s320/makemkv+initial.png" width="320" /></a></div>
<span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span>
<span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">Select the correct optical drive from the list if you have more than one (unlikely these days) and then hit the big button. MakeMKV will then proceed to analyse your disc, which may well take a while - when it's done it'll show you the titles, like so:</span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgElGh1uJbQpFwV04cqYO2_M_Yyr5gZY4oEjORxdyt2Zrjg_JixMLp3L4ywHohiBTzdrgtVWCVk1gieXyvuoSnTarWCrpIr2DvD3N25jAiKAxOCC1TlR1RDOR8iVTWBc_t-uiGkBJ8ViGl/s1600/makemkv+titles+dvd.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="223" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgElGh1uJbQpFwV04cqYO2_M_Yyr5gZY4oEjORxdyt2Zrjg_JixMLp3L4ywHohiBTzdrgtVWCVk1gieXyvuoSnTarWCrpIr2DvD3N25jAiKAxOCC1TlR1RDOR8iVTWBc_t-uiGkBJ8ViGl/s320/makemkv+titles+dvd.png" width="320" /></a></div>
<span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span>
<span style="background-color: white; color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">There may be as few as one title, or tens of titles depending on the disc. If your DVD contains a film, then you want to select the title with the largest size (in this case that's the first one, 6.2GB.) If it's a TV show you're ripping, you should have selected one title for each episode, and they should all be around the same size (ish.) The other titles are likely extras we don't care about, so we'll deselect those. If there's any titles you're unsure about, just include them anyway - it'll take a bit longer to rip, but that's better than doing the whole disc again! </span></span><br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">Expand the view for your chosen title, and you'll likely see something like this:</span></span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTo8e-m5o5S1eq1M-aEKzzD0_wE5b75QxG5jYVjeW-rpmg7gkyjn_sx2my9mVRO2FkBF8h7ZJj2P0Ntc-i9PSa58nwmXD65Hivn9Kt0P28PM4jsK5PUBJ_DbZXv34ztYHd789BFj5T5x0l/s1600/makemkv+titles+dvd+expanded.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="223" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTo8e-m5o5S1eq1M-aEKzzD0_wE5b75QxG5jYVjeW-rpmg7gkyjn_sx2my9mVRO2FkBF8h7ZJj2P0Ntc-i9PSa58nwmXD65Hivn9Kt0P28PM4jsK5PUBJ_DbZXv34ztYHd789BFj5T5x0l/s320/makemkv+titles+dvd+expanded.png" width="320" /></a></div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">Each title contains various tracks, and it's these tracks that you're seeing here - the video track is the one at the top (which you definitely want!) there's audio tracks in different languages, and various subtitle tracks. However, I'm not interested in most of these, so I can deselect them. Presuming you're English go ahead and deselect all apart from the audio track that says "English" and the subtitles tracks that say "English" (do this even if you don't care about subtitles, I'll explain later, honest!) Careful if there's more than one audio track that says English that you select the right one (there may be a couple of others, for instance directors commentaries or audio subtitles. The one you want is usually the first "English" one on the list, and usually the one with the highest quality audio (it's often the only one to have surround sound for instance.)</span></span><br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">For Blu-rays the view looks slightly different but essentially the same:</span></span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGkSZyBN2Bg6civxtPbnpNkAZDGa9QORwwpRLIHPcNT0LiAelHy2nxASIYSKdveppETn1Ge5aJyEv_ir4Nquz-D3g5DgxYpXq4vz5ybd32myvT26zzexXp7uNOyzAlL7xPksbsft6oHujJ/s1600/makemkv+blu+ray.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="223" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGkSZyBN2Bg6civxtPbnpNkAZDGa9QORwwpRLIHPcNT0LiAelHy2nxASIYSKdveppETn1Ge5aJyEv_ir4Nquz-D3g5DgxYpXq4vz5ybd32myvT26zzexXp7uNOyzAlL7xPksbsft6oHujJ/s320/makemkv+blu+ray.png" width="320" /></a></div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">The thing to make sure of here is that you select the "DTS-HD Lossless English" audio as well as the normal "DTS 3/2+1" subitem, to make sure you copy all the uncompressed audio across from the disc. You also have a better option with subtitles, in that you can just tick "English (forced only)" as I have here, rather than checking all the tracks.</span></span><br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">Select an output folder for the files (I usually make a temporary folder on the desktop to store them in) then hit the Make MKV button, and wait! It has to pull all the data off the disc, so will take quite a while. On my machine it's usually around 20 minutes for a DVD, up to an hour for a Blu-ray. Yours may be longer though, so be patient!</span></span><br />
<br />
<h3>
Test the MKV files</h3>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">When MakeMKV is finished, open each file in VLC and check it's what you think it is - you can then delete any MKV files from titles you don't want, and if you like rename any MKV files so you know what they are more easily (episode 1, episode 2) for instance.</span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">You should now have a rip of whatever DVD or Blu-ray you chose, congratulations! However, take a look at the file size - it's likely huge (it may be around 6 or 7GB for a DVD, and around 30GB for a Blu-ray.) Rip your whole collection and leave it like that, and unless you have access to a colossal amount of storage, you'll run out of space rather quickly! You may also not want an MKV file, you may want something to play on your ipod or similar. The next steps will involve reducing the file size down to around 1/3 of what it is at the moment with no noticeable loss in quality (there are various tradeoffs in this sense over file size and quality, we'll get to those later.)</span></span></div>
<br />
<h3>
Checking the subtitles</h3>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><b>This is important! </b>This is the part that most guides miss, but can unknowingly wreck many films if you miss this bit out! What we'll do here is check whether the film has any forced subtitles, and if so translate them into a better format.</span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">Forced subtitles are those that appear even when subtitles are normally disabled, and usually contain captions to translate foreign languages spoken by characters in a film. If you're sure that your chosen film or TV show contains no such captions then you can skip this step, but if you're not sure then follow it anyway to check, otherwise your rip may have large parts you can't understand! <a href="https://docs.google.com/spreadsheet/ccc?key=0AkGO8UqErL6idDhYYjg1ZXlORnRaM3ZhTks4Z3FrYlE#gid=0" target="_blank">This page</a> gives a rough idea of some films that have forced subs, but it's by no means comprehensive.</span></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">When MakeMKV is finished, you should have a folder with an MKV file in for each title (for films, this will likely just be one MKV.) Fire up the MKVExtract GUI, and drag the MKV file onto it. You'll get a screen that looks a bit like this:</span></div>
</div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhno7onwN0KLObQ3menbwzpZMzrWDpdH9TngPgltEdbO5APIL2QegCg9YBio0AbRfdvaRm8JJ42OC031T6wNSfa4UD_R8NCD3hfRjCFWkNdNhSmNEPGm54rLBxn99FOTvqP3vZFCmSYGJRy/s1600/mkvextract.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="273" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhno7onwN0KLObQ3menbwzpZMzrWDpdH9TngPgltEdbO5APIL2QegCg9YBio0AbRfdvaRm8JJ42OC031T6wNSfa4UD_R8NCD3hfRjCFWkNdNhSmNEPGm54rLBxn99FOTvqP3vZFCmSYGJRy/s320/mkvextract.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">Select each track that says subtitles, make sure "use source dir for output" is checked and then hit "Extract." (If you hit "English (force only)" and had a Blu-ray disc, and there's no subtitle tracks here, that's fine - it's because there's no forced subtitles and you can skip this step.) When it's done, you should then have a .idx and a .sub file (just a .sup file if you're doing this for a blu-ray) for each subtitle track in the same folder as the mkv file. Examine the size of these files (ignore any idx files for this step.) Take the largest file, which will probably be the full subtitles for the film. Now have a look at the other sub / sup files, is there any one that's significantly less (less than half the size) of the others? If there is, you've likely found your forced subtitle track, and this is the one you'll want to include in your final rip. If not and the files are all around the same size, then you can likely skip the rest of this step and continue.</span><br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">However, there are times when the forced subtitles are mixed in the same track as the normal subtitles (rare, the only discs I've found that do this thus far are the Game of Thrones DVDs), so if that's the case you'll need to export the entire file and just save the forced subs to the srt file (covered later on.)</span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">This is a typical example that does have a forced subtitle track from an episode of Game of Thrones (Blu-ray):</span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRpa9EZ7TIfVMRJYOb_NNDynnZVoWwd1wt_fGSWv5X5xntMTzEO5Rhb39RaQHxinBNbyQEYjlohKwAVwxpX3TBJ-okVuWqrbDmUWNtGplcSSUBAtB-PP5otRDy0C4oXGqFX-xLTY7U3IY4/s1600/sup+files.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="166" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRpa9EZ7TIfVMRJYOb_NNDynnZVoWwd1wt_fGSWv5X5xntMTzEO5Rhb39RaQHxinBNbyQEYjlohKwAVwxpX3TBJ-okVuWqrbDmUWNtGplcSSUBAtB-PP5otRDy0C4oXGqFX-xLTY7U3IY4/s400/sup+files.png" width="400" /></a></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">We can clearly see two sup files, one around 16MB and the other around 44KB. It's the late 44KB one that's the forced track in this case, used when the Dothraki are speaking (Game of Thrones viewers will know what I mean!) This is a particularly extreme example - the forced subtitle track only has two lines, so in many cases it may be larger (but in almost all cases, significantly smaller than the main subtitle track.)</span></div>
<div>
<br /></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">If you used a Blu-ray and clicked the forced only option, you can bypass this check - if there is a sup file there then you have your forced track in front of you. If not, there were no forced subtitles you need to worry about, and you can skip this step.</span></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">Of course, I'm assuming here that you don't want the full subtitle track; you may well do, for instance if the film is in a foreign language throughout - in this case, do the following for each subtitle track you want in your final rip.</span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">You could just leave it at that, but here (this is the <i>comprehensive </i>guide, after all!) we're going to convert to SRT format. SRT subtitles use text as the format rather than an image, which gives a number of advantages - it takes up much less space than an sub / sup track, you can customise the colour / font / size of the subtitles trivially within the application that's playing them, you can trivially make any corrections if there's spelling mistakes (has been known to happen!) and they will be rendered smoothly on any resolution screen. In the case of DVDs, SRT files also look much better on any modern monitor or television, since they're rendered in the native resolution rather than displayed in scaled up, blocky 704x576.</span><br />
<br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">Load up Subtitle Edit, and open the sub or sup file. You'll be presented with a screen similar to this:</span><br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGFm_-OSTMSf6-hXAMLFyskJO-e1FnVJY3m4xj8WScpR296DxQQzoh-FldMsvk3rAIf9UB84Bj2b3PvbrE5F3EemqoT5hmggMJ803FeROfTKTLeBCCSmhF2hA68BzOcp1Ot9c0J_RxPk7A/s1600/se+import.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="178" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGFm_-OSTMSf6-hXAMLFyskJO-e1FnVJY3m4xj8WScpR296DxQQzoh-FldMsvk3rAIf9UB84Bj2b3PvbrE5F3EemqoT5hmggMJ803FeROfTKTLeBCCSmhF2hA68BzOcp1Ot9c0J_RxPk7A/s320/se+import.png" width="320" /></a></div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">Hit the "Start OCR" button, then subtitle edit will go through and OCR the entire subtitle file. For most foreign language subtitles this won't take long at all - you'll see it as it goes through, and if it gets stuck it'll prompt you to check that it's got it right (or provide a correction if it hasn't.)</span></span><br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">If you're converting a track that has a mixture of forced and non-forced subtitles, and you just want the forced (foreign language) subs, then you'll need to check the "show only forced subtitles" checkbox.</span></span><br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">When done, you'll be taken to the main subtitle edit window with the subtitles displayed. All you need to do now is save it (File -> Save), - ".srt" should be selected as the file type by default, but if not, change it so it's definitely in srt format rather than another type.</span></span><br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">Repeat this process for each MKV file you have - in the case of TV shows, this may be a few per disc. When you've got more than one title to do, make sure you know what subtitle file goes with what episode!</span></span><br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span></div>
<div>
<h3>
Transcoding / Compressing</h3>
</div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"> Almost there - just one more step! Open up handbrake and drag your MKV file onto it (ignore any warning that appears about automatically named output files.)</span><br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">If you have a subtitles track (srt file as created above), click on the subtitles tab, click import SRT, and locate your srt file you saved earlier. Make sure the "Default" checkbox is selected (important!), and you should end up with something like this:</span><br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEoqllLW5j80nwbv9Y2AeU2yqrMv5tbJ-lhxLW9nApR7IWvJODIY_7Ndevwdk4HbPCP7F8XmQe_ozDWjOqXF1_VOQGNt7CjflMZd2zf5JS00RA5CPbnB0mcLfBZuQEbU2-SjmjrCGPVciX/s1600/handbrake+sub.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="214" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEoqllLW5j80nwbv9Y2AeU2yqrMv5tbJ-lhxLW9nApR7IWvJODIY_7Ndevwdk4HbPCP7F8XmQe_ozDWjOqXF1_VOQGNt7CjflMZd2zf5JS00RA5CPbnB0mcLfBZuQEbU2-SjmjrCGPVciX/s320/handbrake+sub.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">When you've sorted the subtitles out, click the "Browse" button and select your destination location.</span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><b>Don't click on "Start" just yet. </b>You could, and you'd end up with a perfectly playable MKV or MP4 file on handbrake's default settings, but it may not be what you want.</span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">Are you ripping this specifically for a device? If so, click on the preset on the right hand side and then click Start. These should encode relatively quickly, if you're unhappy with the result then just re-encode with the Video Quality slider set a bit higher - find it on the video tab (set it to something like 19 instead of 20 - yes, a lower number is better quality!)</span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">However, if you're just ripping it to have as part of a media centre (which is what I do) then I'd recommend my process (see Note 2), which is adjusting the settings from Handbrake's default to the following:</span></div>
<div>
<ul>
<li><span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">Head over to the "Video" tab, and set the slider value there to 19. (I notice some blocking especially on fast action films when you leave it at 20, but setting it to 18 drastically increases file size. You may wish to experiment to find what works best for you, your eyes may be better or worse than mine!)</span></span></li>
<li><span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">If you want to preserve surround sound (I do) then head over to the "Audio" tab, and under the "Codec" drop down option for the track, select Auto-passthru. You should end up with something like this:</span></li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhC9RpY1i9wR3DPpeEkFSEnoT47vG20NAsSzhLEg3RlonvnsktVJtuwH0rzYxVp4WfaIgPpxH__EtjwAGkiKKTMIg-rYGBYyXZ8FXyEFkWfje26bYUjJinSo8WEOVePAI3oz_f2dDBwgAUF/s1600/handbrake+aud.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="214" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhC9RpY1i9wR3DPpeEkFSEnoT47vG20NAsSzhLEg3RlonvnsktVJtuwH0rzYxVp4WfaIgPpxH__EtjwAGkiKKTMIg-rYGBYyXZ8FXyEFkWfje26bYUjJinSo8WEOVePAI3oz_f2dDBwgAUF/s320/handbrake+aud.png" width="320" /></a></div>
<div class="separator" style="clear: both; color: #222222; font-family: Arial, Verdana, sans-serif; font-size: 12px; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; color: #222222; font-family: Arial, Verdana, sans-serif; font-size: 12px; text-align: justify;">
<i>This is important if you want surround sound - if you leave it at the default option it'll be downmixed to a reduced quality stereo. I only have a stereo setup of speakers at the moment, but I do this because if I did get a surround sound system, re-encoding every single DVD just so I can have surround sound would be a nightmare - better to do it right the first time! I'd suggest you do the same, unless you know you really definitely will never need surround sound in your rips and/or you want your rips to be as small as possible.</i></div>
<div class="separator" style="clear: both;">
</div>
<ul>
<li><span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">Select "MKV file" as the container - it's technically much more flexible and generally better than MP4, and all good media players now support it. (See Note 1 if you're after avi.)</span></span></li>
<li><span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><i>Optional: On the "Video" tab, selecting H265 as the "Video Codec" will result in much smaller files for the same quality - I now use H265 for all my rips, and there's often around a 30% size reduction. This has a time tradeoff though (see Note 3.)</i></span></span></li>
<li><span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">Head back to the video tab, and under the "Optimise Video" group, drag the preset slider until it gets to "Very Slow":<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2_IhyphenhypheneUX_921ievvSKb63KLyOZCPxnB8jXWD7PBGwfyVPSMIOQNdNI-wctuTVer1PfXUAwl1n6Fs6cUoMcz0mpWUVNGX7GW5DEU7NApKhQEms2qp5cDPrGAC8HvdPiTHPkoAIIMjoeQQ1/s1600/handbrake+video+opt.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="250" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2_IhyphenhypheneUX_921ievvSKb63KLyOZCPxnB8jXWD7PBGwfyVPSMIOQNdNI-wctuTVer1PfXUAwl1n6Fs6cUoMcz0mpWUVNGX7GW5DEU7NApKhQEms2qp5cDPrGAC8HvdPiTHPkoAIIMjoeQQ1/s320/handbrake+video+opt.png" width="320" /></a></div>
</span></span></li>
</ul>
<div>
<pre></pre>
</div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><b>Then you can hit "Start", and wait for your video to encode!</b></span></span><br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">When Handbrake is done, then there you have it - your very own DVD or Blu-ray rip! Just to make sure everything went smoothly though, it's best to double check things worked ok.</span></span><br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span>
<br />
<h3>
Check the subtitles!</h3>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">Yup, we're back with subtitles again, though this time it's just a precautionary measure - check that any forced subtitles you're expecting to show up, actually do show up. There seems to be a bug in Handbrake with subtitles that means under certain conditions (for me this happens often with Blu-rays but not with DVDs) they won't always get written in properly.</span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">If this happens, then fire up MKVMerge (see "joining MKV files" for a description) and load in your transcoded file, deselect the text stream that's in the file, then click "add" and add in your SRT file. MKVMerge (at least under all conditions I've tested it with) seems to do this correctly every time, unlike Handbrake.</span></div>
<br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span>
<br />
<h3>
Joining MKV files</h3>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">Most of the time you won't need this step at all - it only applies to a few films I know of (such as the Lord of the Rings extended editions.) But this is where ripping can really have an advantage, because there's no need to swap discs over half way through, or even MKV files - you can rip the discs separately and then join them together in one continuous file. To do this, you need to rip both discs with *exactly* the same settings all round in Handbrake (so if you follow the above guide to the letter you should be fine) and then use a tool called MKVMerge. (This is a fantastic tool that also lets you add things like extra subtitle and audio streams after you've ripped the file, but here we'll be using it to join two MKV files together.)</span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">MKVMerge is part of the MKVToolnix suite, which you can grab from <a href="http://www.downloadbestsoft.com/MKVToolNix.html" target="_blank">here</a>. When it's installed, you want to run the "MKVMerge GUI".</span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">Once you've got it running, appending the two files together is simple and doesn't take long at all. Just click the add button, then select the first MKV file, then click append (<i style="font-weight: bold;">Note: that's append the second time not add!</i>)<span style="font-weight: bold;"> </span>and select the second MKV file. You should end up with something that looks like this:</span></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiD-yIap1572cmTWXCNmWLZFedHaPrtaVztq5En87G7T6P156sg_i5d2_sX54dekVFzz29P-CT8fNMDAvGyONKJV4kXD1wAWRn1gUM1fC0CcLntk90RkqnvDvX15Wu0GjqxAVhS8MpNdZ7/s1600/mkvmerge.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="395" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiD-yIap1572cmTWXCNmWLZFedHaPrtaVztq5En87G7T6P156sg_i5d2_sX54dekVFzz29P-CT8fNMDAvGyONKJV4kXD1wAWRn1gUM1fC0CcLntk90RkqnvDvX15Wu0GjqxAVhS8MpNdZ7/s400/mkvmerge.png" width="400" /></a></div>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></div>
<br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">Select the output filename (towards the bottom of the window), then click on "Start muxing" and the program will join the two files seamlessly. If it spits back any errors, it's probably becase the two files weren't encoded with exactly the same settings - so double check and try again.</span></span><br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span>
<br />
<h3>
Wrap-up</h3>
<div>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">This may have seemed like an incredibly long process for ripping a disc, and some will say it's overkill. However, while it might take a while the first time, after ripping a few discs this way the process sped up significantly. When you get used to what to do, it really doesn't take long at all, and the results can be anything from a convenient ipod-playable file to a high quality home media centre setup where you can search through and browse available films using something like Kodi or Plex. Setting that up isn't too complicated in itself either - but that's for another post!</span></span><br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span>
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">-------------------------------------------------------------------</span></span><br />
<span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;"><br /></span></span>
<i><span style="color: #222222; font-family: "arial" , "verdana" , sans-serif;"><span style="font-size: 12px;">* Note 1: </span></span><span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">A lot of people seem to ask "Where's AVI?!" at this point, and get very upset when they can't find it. Let go! Seriously, don't bother with it, it's a horrible antiquated format that can't hold separate tracks, can't support modern codecs and can't support any files larger than 2GB (so it's absolutely useless for Blu-ray encodes especially, and very very limited even for DVD rips.) It may be what you're used to, but these days it's really awful. Don't go looking for it, Handbrake doesn't support it any more! You can of course select MP4 if you need it (usually if the device you're playing it on doesn't support MKV.)</span></i><br />
<i><span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></i>
<i><span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">* Note 2: </span><span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">How you set up handbrake is very much a personal choice, there's no right answer so don't be afraid to experiment around with the options. I've detailed there the options that I use, including the "Very Slow" preset. This deliberately takes a long time to do the encode, but produces (pretty much) the best quality / filesize ratio. There is one beyond this - placebo. The placebo preset takes this to extremes, but because of diminishing returns, it's really not worth it at all. You might have a 0.000001% increase in filesize : quality ratio if you're lucky, and the encode time will go from a few hours to a few days, hogging up 100% of CPU for all that time and having to be resumed from square one if anything goes wrong or your computer decides to run updates and restart...! You may wish to go for a faster encode if you want results more quickly, but "Very Slow" is really the slowest practically useful encode.</span></i><br />
<i><span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;"><br /></span></i>
<i><span style="color: #222222; font-family: "arial" , "verdana" , sans-serif; font-size: 12px;">* Note 3: H265 is the successor to H264, and will generally give you rips less than 2/3 the size of those with H264 for the same quality. As such I now use H265 for all my rips - but be aware that this takes much, much longer. On the "veryslow" preset I use, the average DVD now takes upwards of 24 hours (it took around 3-4 before.) Also note that fewer devices will support H265, though adoption is now pretty rapid - the major media platforms such as VLC, XBMC and GStreamer all support it fine.</span></i></div>
</div>
</div>
Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com36tag:blogger.com,1999:blog-4169375346527088512.post-12781385831850743062013-03-05T07:26:00.003-08:002013-03-05T07:26:50.120-08:00Annoyingly named methodsThere's several annoyances I have with method names in the Java API - some are well documented as being ridiculous early design decisions that, well, couldn't be reverted without breaking backwards compatibility. Boolean.getBoolean() is a classic example for instance that really doesn't do what you'd expect.<br />
<br />
However, there's another which is more recent that I haven't really heard complained about anywhere else. It's not a deal breaker by any stretch of the imagination, but it is a bad name, and it is mildly annoying.<br />
<br />
I'm talking about the removeAll() method present on collection classes. It takes a collection of items to remove from that particular collection - pretty standard stuff. But to me, that should be called removeEach(), removeAllOf(), or perhaps even just remove(), overloaded with the standard method that takes one parameter (to me the latter seems like the most logical choice.)<br />
<br />
The problem I have with removeAll() is that it sounds like a method that you would call to do just that - remove all items from the collection. Wipe it. Now this isn't too bad, because if you get it wrong and call it with no arguments, it doesn't compile, and you can fix it pretty easily.<br />
<br />
With JavaFX however, they've very helpfully added a var-args method with this name, that <a href="http://docs.oracle.com/javafx/2/api/javafx/collections/ObservableList.html#removeAll(E...)" target="_blank">does the same thing</a>. This may seem like a logical extension, but because it's a var-args method, passing <i>no</i> parameters, which does nothing, is a perfectly valid option. Not just at compile time, but at runtime as well. It all goes fine, apart from the method does absolutely nothing. It fails silently.<br />
<br />
Perhaps a better implementation would have been to make it a two argument constructor, a "normal" reference to a parameter of type E, and then a var-arg list afterwards, essentially making it a "one or more" var-arg list. Granted, this would remove the ability to directly pass an array in - but an overloaded method expecting an array of that type could easily be added to get around that issue.<br />
<br />
Anyway, rant over. Back to playing around with cubic Beizer curves for me!Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0tag:blogger.com,1999:blog-4169375346527088512.post-7268439203664084582012-12-11T07:44:00.001-08:002012-12-11T07:44:35.072-08:00JavaFX - some later thoughtsI've been using JavaFX for a while now, in a couple of projects - for my PhD work, and on the side for <a href="http://quelea.org/">Quelea</a> (whose interface has been migrated entirely from Swing over to JavaFX.) As promised, I thought I'd post a few follow up thoughts - good points / bad points now that I've been using it a while.<br />
<br />
So, without further ado, the positives!<br />
<br />
<ul>
<li>The API is nice and clean - and not just because it's not stuffed with deprecated methods like Swing is, it's just designed in a fundamentally much more sensible way which makes it easier to follow. The concept of properties on elements, with these properties having a common interface, means I can jump right in with a component and see what properties are available to me without having to dig in the documentation to find out exactly how to add a listener for the width of the bottom half of my split pane (for instance.)</li>
<li>Layout is vastly improved - a lot of the swing inconsistencies have gone, and the model is now a specific component is tied to a specific layout (HBox, VBox, GridPane and so on) rather than having a separate content pane which can have a layout applied to it. This again ties in with the first point, making for a much easier, nicer API without having to wonder exactly what, if any parameters I need to shove on the end to get the layout manager which I've selected (and what one was it, anyway?) to behave.</li>
<li>You can actually do animations with keyframes without resorting to horrible graphics2d hacks - and it's all GPU accelerated.</li>
<li>Multimedia support comes as standard, no need to play around with the buggy as hell Javasound or JMF.</li>
<li>UI work on the platform thread is enforced most of the time - a runtime exception is thrown if you don't do this, which makes it much easier to find and solve odd annoying concurrency bugs that would often crop up otherwise.</li>
<li>The default cross platform skin doesn't make me want to vomit. On the contrary, it actually does a good job of looking rather nice across a range of platforms. It's visually appealing and nicely animated too.</li>
<li>Nice native deployment options - they only work on the current platform (i.e. you can't build a deb package on a windows box) but still, I like the change of thought that users are generally much more comfortable with a custom built package for their OS rather than a generic jar / jnlp file.</li>
</ul>
<div>
Despite the above, it's not flawless:</div>
<div>
<ul>
<li>The multimedia support is great - when it works. But it supports in reality a very limited range of formats and file types (no mkv at all for instance, some of my mp4 encoded videos still didn't work either.)</li>
<li>There's still a few annoying bugs I've come across, such as <a href="http://javafx-jira.kenai.com/browse/RT-22667">this one</a>. Nothing that can't be sorted, but these sorts of things are annoying.</li>
<li>Some features just aren't there yet - I wanted a pop up panel similar to the ColorPane, but those components just don't exist yet (or at least aren't part of the public API.)</li>
<li>No rich text on controls, at least not yet. This gets really annoying if you want to (for instance) bold part of a label. Can't be done, you have to butt multiple labels together in a HBox to get that effect (which, let's face it, is nasty.)</li>
<li>No native skins - you can write your own, but at the moment (as far as I know) none are provided to make it look like the native platform you're working on. Some would argue this is a good thing, but sometimes it's nice to have this option.</li>
<li>No damn font metrics in the public API - again, something I have to use an internal class for at present (which is really rather annoying and means code I write at present potentially isn't backwards compatible.)</li>
</ul>
<div>
Overall, I must admit I still like it, and while I've uncovered more things I don't like from my initial positive reaction, most of those things are slated for inclusion in Java 8 (so in a year or so at the time of writing.) I still don't think it's going to take off on the mobile or web front - but as a replacement for Swing on the desktop, it's a very welcome (and arguably long overdue) change.</div>
</div>
Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0tag:blogger.com,1999:blog-4169375346527088512.post-63876762984086728402012-02-16T07:57:00.000-08:002017-12-16T05:38:18.917-08:00Dropbox Java API<span style="font-size: x-large;">This code will no longer work! It uses the old v1 API, which has been turned off. <a href="http://berry120.blogspot.co.uk/2017/12/an-updated-dropbox-api-example.html">See here</a> for working code with the latest v2 API.</span><br />
<br />
As well as being useful as general cloud storage, dropbox also has an API that lets you access its contents programmatically. It's a straightforward REST API with a number of language specific libraries to make the going a bit easier. Java is included on the list of SDKs, but at present only Android is included on the list of tutorials. This can be somewhat frustrating because simple examples using Java are lacking.<br />
<br />
Fortunately, the process was described by Josh <a href="http://forums.dropbox.com/topic.php?id=48188">here</a>. So I've taken it and implemented it in Java, and it seems to work. It's a very basic example that authenticates with dropbox then uploads a file called "testing.txt" containing "hello world."<br />
<br />
Of course, more functionality is available than this, but this is the hard part (at least I found working this bit out the hard part.) Once you've got your DropboxAPI object, you can work most things from there using the <a href="https://www.dropbox.com/static/developers/dropbox-java-sdk-1.5.3-docs/index.html" target="_blank">supplied Javadoc</a>. (You can find the Java libraries that you'll need to download <a href="https://www.dropbox.com/developers/core/sdks/java" target="_blank">here</a>.)<br />
<br />
<pre class="brush: java; gutter: false;">/**
* A very basic dropbox example.
* @author mjrb5
*/
public class DropboxTest {
private static final String APP_KEY = "APP KEY";
private static final String APP_SECRET = "SECRET KEY";
private static final AccessType ACCESS_TYPE = AccessType.APP_FOLDER;
private static DropboxAPI<WebAuthSession> mDBApi;
public static void main(String[] args) throws Exception {
AppKeyPair appKeys = new AppKeyPair(APP_KEY, APP_SECRET);
WebAuthSession session = new WebAuthSession(appKeys, ACCESS_TYPE);
WebAuthInfo authInfo = session.getAuthInfo();
RequestTokenPair pair = authInfo.requestTokenPair;
String url = authInfo.url;
Desktop.getDesktop().browse(new URL(url).toURI());
JOptionPane.showMessageDialog(null, "Press ok to continue once you have authenticated.");
session.retrieveWebAccessToken(pair);
AccessTokenPair tokens = session.getAccessTokenPair();
System.out.println("Use this token pair in future so you don't have to re-authenticate each time:");
System.out.println("Key token: " + tokens.key);
System.out.println("Secret token: " + tokens.secret);
mDBApi = new DropboxAPI<WebAuthSession>(session);
System.out.println();
System.out.print("Uploading file...");
String fileContents = "Hello World!";
ByteArrayInputStream inputStream = new ByteArrayInputStream(fileContents.getBytes());
Entry newEntry = mDBApi.putFile("/testing.txt", inputStream, fileContents.length(), null, null);
System.out.println("Done. \nRevision of file: " + newEntry.rev);
}
}</pre>
<br />
To use the token pair in future, instead of doing:
<br />
<pre class="brush: java; gutter: false;">WebAuthSession session = new WebAuthSession(appKeys, ACCESS_TYPE);</pre>
<br />
You would do:
<br />
<pre class="brush: java; gutter: false;">WebAuthSession session = new WebAuthSession(appKeys, ACCESS_TYPE, new AccessTokenPair(key, secret));</pre>
<br />
...where key and secret are the ones printed in the example above. You can then skip the rest of the auth code (up until you create the DropboxAPI object.)Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com112tag:blogger.com,1999:blog-4169375346527088512.post-35851108297841159072012-02-15T03:25:00.002-08:002012-02-15T03:35:54.625-08:00Sometimes, older is better...When I first started Quelea, I wanted to use it as a kind of testing ground for features and ideas that hadn't already been incorporated into church presentation software. Both in terms of the UI, features and functionality. A lot of these features have worked really well - the ability to import from survivor songbooks for instance, and the instant search that's further improved in the next release.<br /><br />On the UI side of things I tried to have a go with the ribbon - it's still got a love/hate relationship with people, but I wondered if it could do any good in Quelea. So I had a go, implemented it and left it as such for a few releases.<br /><br />Thing is, it just didn't work - and I think this was a 50:50 split between it not being suitable for Quelea and the flamingo implementation.<br /><br />On the suitable for Quelea side:<div><ul><li>For something that's often run on laptops with small screens, it took up a huge amount of space it didn't need to.</li><li>There weren't enough controls to make it viable. It works (ish) for office because it replaced a hugely complex menu system, but Quelea just doesn't have that complex menu system, and it won't for the foreseeable future. So it really just acted like a huge toolbar.</li></ul><div>And on the flamingo side:</div><div><ul><li>It was pretty unmaintained, which doesn't exactly add to my confidence.</li><li>It only integrated well with the substance look and feel, which users might not want to use.</li><li>It only integrated at all well with Windows, and Quelea is targetted at cross platform use.</li><li>It looked - well, odd. Most bits are there, but odd bits like the tight integration with the Window aren't, and that just makes it feel like something's not quite there with it. Combine this with the first point and it isn't going away any time soon either.</li></ul><div>Perhaps there are some things I could've chosen to make it look better - like designing the UI in SWT rather than Swing (which has a much nicer looking ribbon.) But I've now replaced the ribbon with a standard set of toolbars and menus, and personally I think this looks much nicer. We'll see how it pans out in practice, but sometimes the older, traditional way is definitely the better one!</div></div><br /></div>Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0tag:blogger.com,1999:blog-4169375346527088512.post-25787627101204416652012-01-11T06:42:00.001-08:002012-01-11T07:02:08.114-08:00Java puzzlerYou'll need an understanding of weak references to have a meaningful stab at this one.<br /><br /><pre class="brush: java"><br />public class Test {<br /> <br /> public static void main(String[] args) {<br /> List<byte[]> list = new ArrayList<byte[]>();<br /><br /> Object thing = new Object();<br /> WeakReference<Object> ref = new WeakReference<Object>(thing);<br /> <br /> while(ref.get()!=null) {<br /> list.add(new byte[10000]);<br /> }<br /> System.out.println("bam");<br /> }<br /> <br />}<br /></pre><br /><br />Before reading any further give it a stab, see what you come up with. Then run it and see. Oh and yes, it compiles and runs.<br /><br /><br /><br /><br /><br /><br />...<br /><br /><br /><br /><br /><br /><br /><br />Well, there's two possible options that you could have sensibly come up with. The first is what most people assume will always happen - the byte array will fill up to cause an OutOfMemoryError and the weak reference will never be garbage collected because there also exists a strong reference (thing) that remains in scope throughout the application and thus doesn't "let go" of the object. And indeed if you run it with early versions of Java, this is exactly what will happen.<br /><br />However, with the latest version of JDK7 (update 2 at the time of writing) the program doesn't do the above, it prints out "bam" and exits cleanly. This initially seems weird - there's still a strong reference in scope so why on earth is it garbage collected?<br /><br />It boils down to improvements in the JIT. It realises that the hard reference isn't actually used again, so sets it to null thus making it eligible for garbage collection. Put something like "thing.getClass();" as the last line of the program and it always forces the OutOfMemoryError because if that's the case the JIT can't make this optimisation. It's a behaviour that's actually explicitly covered in the JLS:<br /><br /><pre>Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable. For example, a compiler or code generator may choose to set a variable or parameter that will no longer be used to null to cause the storage for such an object to be potentially reclaimable sooner.</pre><br /><br />http://java.sun.com/docs/books/jls/third_edition/html/execution.html#12.6.1<br /><br />The lesson? Objects can be garbage collected when the JIT decides they're no longer in use, and this may be earlier when it seems - and this in turn means that finalizers can be called when the object might trivially seem in-scope and thus ineligible for collection.Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0tag:blogger.com,1999:blog-4169375346527088512.post-39199270692022745332011-12-31T16:19:00.001-08:002013-12-29T09:30:04.675-08:00AparapiThe massive scalability of modern GPUs has led to many things, from Bitcoin mining to folding@home, seti@home and various other general purpose tasks that involve huge amounts of number crunching. While not as flexible as CPUs, modern GPUs can be hugely scalable thanks to the massive number of stream processors they contain.<br />
<br />
I've long been impressed by the technology, and long known that to implement something using it you can use CUDA and/or OpenCL. For about the same amount of time there have been low level Java wrappers too that mean you can execute OpenCL without resorting to native languages - even if it meant you had to write the OpenCL directly, as well as huge amounts of boilerplate code to interface with it. Of course, if you wanted your application to work in the real world, a completely separate load of code for it to fall back on if the device didn't support OpenCL was needed too.<br />
<br />
For about the same amount of time, I've thought that what we really need is a near transparent layer in (insert high level language of your choice here) which manages all the boilerplate / fallback code, OpenCL generation etc. for you. Of course, I could have sat down and looked at implementing it myself but I decided to wait for someone to do the hard work for me, thinking that it was inevitable that they would.<br />
<br />
Fortunately I was correct, a bit of digging around today turned up Aparapi. It's an initiative started by AMD that enables to bring GPGPU programming to the average Joe Java developer completely transparent (almost) of any OpenCL calls and boilerplate. All it requires is for you to write your code inside a fabricated closure (anonymous inner class at present) and then call an execute method for the number of times you want it to run, in parallel. You can get the index of your essentially parallelised *loop* from within the kernel and crunch away on the GPU.<br />
<br />
When lambdas come out of course there will be less boilerplate still - essentially having compresssed the hundreds of lines previously required to do this sort of thing with no more than a couple. Suddenly we've gone from stupidly hard and not worth doing to about as easy as it can get.<br />
<br />
I envision this to be the first, but most important (and arguably hardest, but perhaps that's because I'm not a low level guy) step in providing the sort of transparent GPGPU access to Java that I continually dream of. The next step would be for API designers to take advantage of this technology in turn to produce classes like ParallelBufferedImage for instance. When this comes then ordinary Java folk will be able to utilise hugely parallel operations without so much as lifting a finger. We won't have to do anything different and all such API lifting will be done in the background, creating a huge (in some cases) but near invisible performance boost, just using one class rather than another.<br />
<br />
Following on from that, the final step if it really takes on would be for it to be included in the API as standard. Possible? Sure. Likely? Who knows, but if so then it'd truly expose GPGPU to the masses.<br />
<br />
Of course, that doesn't prohibit playing around until my completely transparent API-level pipe dream comes into play. Me being me I haven't actually got an OpenCL compatible card to try it out (I tend to live in the past a bit on the gaming front) but am thinking of ordering one just to have a play! And next step (naturally) is to see what we can integrate into Quelea to potentially speed that up with GPU hardware. It might even make my idea of keystone correction in software viable, we shall see! Perhaps 2012 will bring us a few more developments. Watch this space!Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0tag:blogger.com,1999:blog-4169375346527088512.post-87432850618238756712011-11-09T00:58:00.000-08:002011-11-09T09:04:48.790-08:00JavaFX 2I originally took a quick look at the original JavaFX when it was first announced. When I heard the concept I thought it sounded interesting to potentially use on the desktop front but on the web front it was a no-no. Perhaps I'm wrong, but I honestly think HTML5 will rule the roost there and JavaFX is really too late. I also really didn't like the scripting language - some people do and I can see it's benefit, but for an application that I'm creating fully it created a bit of unnecessary complexity in my opinion.<br /><br />Anyway, this was all a few years back now and for a number of reasons, but primarily because it looks a promising technology for an aspect of my PhD, I've been giving it another look. I've wanted to ever since I saw the impressive <a href="http://www.youtube.com/watch?v=UXSmJYFrulY">keynote demo</a> last year, but hadn't had the time to really get into it. For the last few weeks however that's changed and I've been playing away!<br /><br />Initial impressions, I have to say, are good. It is now of course fully Java, the scripting language has gone. I expected after playing around with it for a while I'd find various bits of the API I didn't like or were missing, had to be done in an awful way, inconsistencies and so on. But that's not the case (at least not so far.) The API is very clean and consistent, much more so than swing, but a lot of the good design elements from swing carry across. A lot of the same design patterns are used and therefore have a similar methodology for events and suchlike; a lot of the panes will be familiar (BorderPane for instance is akin to the old BorderLayout on Swing) and so on. The names of the components do conflict with the old AWT model, as in a button is just called "Button", not "FXButton" or similar, but I like that - as long as you make sure the right things are imported it works well and saves a pointless FX prefix just because an archaic technology once had no prefixes!<br /><br />While you can of course do what used to be done with swing, creating the normal GUIs, JavaFX is much more flexible. It works as a scene graph where everything is a node that can be attached, edited or detached. This makes a lot of things that were awkward and slow remarkably simple and fast (it's all hardware accelerated) in JavaFX. Even something as simple as moving a circle across the screen previously required dealing with loads of BufferedImages, redrawing everything at a certain point then refreshing it, then implementing double buffering to get rid of the flicker, and even then CPU usage was way higher than it should've been for something trivial. With JavaFX though that's all done for you under the hood in a much nicer way. Want to create an effect on something? Simply call the setEffect() method with a number of built in effects such as blurs, shadows and so on, or create your own. Want to move something around? Simply create a timeline that defines a number of key frames using various properties, then call the play() method on the timeline. No double buffering issues, speed issues, refresh issues, threading issues... when you're used to crowbarring those sorts of things into place it is a very welcome break!<br /><br />Of course, I mentioned earlier that everything was a node, which is true - this includes the normal GUI components like buttons, not just arbitrary shapes like circles. So if you want to give a button a drop shadow effect, you just call setEffect(new DropShadow()); as you would on any other node. And if you want a button to subtly fade colour in and out, you can implement it easily using a timeline.<br /><br />The only criticism I have, and this isn't really a criticism of the framework itself, is that because it's so new there's very little material around on it. No books are out until next year, there's only really the official Oracle tutorials (though they are good) and the rest of the time you really have to ask the questions yourself if you want the answers. But like anything along these lines, that does increase with time - and if you do look at the old JavaFX 1 stuff, a lot of the concepts can be transferred across (a lot of the properties have stayed similar for instance just shifted languages.) And it does seem to be rather slow at taking off cross platform - so far there's just a dev preview for Mac and nothing for Linux.<br /><br />In terms of adoptability (coined word there) it's compatible with swing in that you can mix the two now - so transferring applications across gradually is also an option.<br /><br />With Java 8 though it should be included as standard hopefully - potentially confining Swing to the legacy zone, which I personally would welcome! It's a nice framework, clearly thought out and very nicely designed. I'm sorely tempted to use it for any new apps I'm writing now, and as it becomes more standard I'm more tempted to port existing applications across.Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0tag:blogger.com,1999:blog-4169375346527088512.post-75758339702232291902011-07-31T15:27:00.000-07:002011-07-31T16:11:08.173-07:00Using VLCJ for video reliably with out of process playersThis post is really a follow on to the last, so if you want a bit of background give that a read through. In it I basically talk about the various options I came to when trying to implement video support in Java, and how I felt the best way to go was using out of process players and VLCJ.<br /><br />Before I get to the code, a warning - while this isn't stupidly hard, it's definitely not for beginners. If you're competent with Java in general you shouldn't have any trouble following - but this is not a cut / paste / forget the advanced stuff job! Eventually and depending on interest I may look into packaging it up into an easy to use library and distributing, but that day is not today!<br /><br />Secondly, this code is literally cut straight out of Quelea and at the moment is:<br /><ul><br /><li>Largely uncommented</li><br /><li>From an unreleased version</li><br /><li>Not the best quality</li><br /><li>May contain Quelea specific bits that don't apply</li><br /></ul><br />Eventually, it'll all be refactored into beautiful OO-like niceness. But in terms of the concepts, it should be enough to get them across.<br /><br /><h2>An overview</h2><br />Essentially, how it works is by firing off a separate process executing all the native VLC code. The references to the standard output and standard input streams of the "other" process(es) are saved, and these are used for communication between the processes. (It could just as easily be done by sockets, shared memory magic, some generic RMI framework and so on. But this for me is a scalable solution that just works without any substantial libraries or potential problems with firewalls that you can get with sockets.)<br /><br />There's a rudimentary protocol that maps commands sent between the streams to the actions the out of process player should take, or the values it returns. The API user doesn't need to worry about these, since it's all encapsulated in an easy to use class, <code>RemotePlayer</code>.<br /><br />I'm using this approach to run 3 concurrent media players in Quelea, and I've had 0 VM blowouts thus far in all the trial runs I've done (which is a fair number!) The guy in charge of VLCJ also reports the same behaviour when using out of process players. However you accomplish it, this seems undoubtedly the way to go if you want to prevent your application from crashing.<br /><br /><h2>The actual code bit</h2><br />So, now actually onto the code. Where it all starts from the user's point of view is RemotePlayerFactory:<br /><br /><pre class="brush: java"><br />package org.quelea.video;<br /><br />import com.sun.jna.Native;<br />import java.awt.Canvas;<br /><br />public class RemotePlayerFactory {<br /><br /> public static RemotePlayer getRemotePlayer(Canvas canvas) {<br /> try {<br /> long drawable = Native.getComponentID(canvas);<br /> StreamWrapper wrapper = startSecondJVM(drawable);<br /> final RemotePlayer player = new RemotePlayer(wrapper);<br /> Runtime.getRuntime().addShutdownHook(new Thread() {<br /> @Override<br /> public void run() {<br /> player.close();<br /> }<br /> });<br /> return player;<br /> }<br /> catch (Exception ex) {<br /> throw new RuntimeException("Couldn't create remote player", ex);<br /> }<br /> }<br /><br /> private static StreamWrapper startSecondJVM(long drawable) throws Exception {<br /> String separator = System.getProperty("file.separator");<br /> String classpath = System.getProperty("java.class.path");<br /> String path = System.getProperty("java.home")<br /> + separator + "bin" + separator + "java";<br /> ProcessBuilder processBuilder = new ProcessBuilder(path, "-cp", classpath, "-Djna.library.path=" + System.getProperty("jna.library.path"), OutOfProcessPlayer.class.getName(), Long.toString(drawable));<br /> Process process = processBuilder.start();<br /> return new StreamWrapper(process.getInputStream(), process.getOutputStream());<br /> }<br />}<br /></pre><br /><br />The call that might be most unfamiliar initially is the JNA one - Native.getComponentId(). Every heavyweight component has an ID which is assigned at the OS level and used for drawing to a particular window. It's this component ID that we'll be passing to the separate process, which it will use to draw to the window owned by the parent process. Notice also the shutdown hook so the other VM is terminated when this one is (that's essentially what the close method does, more on that later.) It's not a foolproof approach but it's good just in case it doesn't get cleared up otherwise.<br /><br />In terms of the startSecondJVM method, this is a pretty standard, cross-platform (as much as I care about anyway, Windows, MacOS and Linux should all be fine) method to start up a second JVM. It starts the <code>OutOfProcessPlayer</code> with the component ID as its argument. There's just a few differences - firstly, we copy over the classpath and JNA library path of this VM so it can execute the class in this project which executes native VLC code without any problems. Secondly, it captures the streams in a StreamWrapper object, which is just as follows:<br /><br /><pre class="brush: java"><br /><br />package org.quelea.video;<br /><br />import java.io.InputStream;<br />import java.io.OutputStream;<br /><br />/**<br /> *<br /> * @author Michael<br /> */<br />public class StreamWrapper {<br /><br /> private InputStream inputStream;<br /> private OutputStream outputStream;<br /><br /> StreamWrapper(InputStream inputStream, OutputStream outputStream) {<br /> this.inputStream = inputStream;<br /> this.outputStream = outputStream;<br /> }<br /><br /> public InputStream getInputStream() {<br /> return inputStream;<br /> }<br /><br /> public OutputStream getOutputStream() {<br /> return outputStream;<br /> }<br />}<br /></pre><br /><br />Nothing special here, it's literally just wrapping up the input and output streams.<br /><br />Onto the two core classes here, RemotePlayer and OutOfProcessPlayer. As the name suggests, the latter is the one that sits out of process.<br /><br />RemotePlayer.java:<br /><pre class="brush: java"><br /><br />package org.quelea.video;<br /><br />import java.io.BufferedReader;<br />import java.io.BufferedWriter;<br />import java.io.IOException;<br />import java.io.InputStreamReader;<br />import java.io.OutputStreamWriter;<br /><br />/**<br /> * Controls an OutOfProcessPlayer via input / output process streams.<br /> * @author Michael<br /> */<br />public class RemotePlayer {<br /><br /> private BufferedReader in;<br /> private BufferedWriter out;<br /> private boolean open;<br /> private boolean playing;<br /> private boolean paused;<br /><br /> /**<br /> * Internal use only.<br /> */<br /> RemotePlayer(StreamWrapper wrapper) {<br /> out = new BufferedWriter(new OutputStreamWriter(wrapper.getOutputStream()));<br /> in = new BufferedReader(new InputStreamReader(wrapper.getInputStream()));<br /> playing = false;<br /> open = true;<br /> }<br /><br /> private void writeOut(String command) {<br /> if (!open) {<br /> throw new IllegalArgumentException("This remote player has been closed!");<br /> }<br /> try {<br /> out.write(command + "\n");<br /> out.flush();<br /> }<br /> catch (IOException ex) {<br /> throw new RuntimeException("Couldn't perform operation", ex);<br /> }<br /> }<br /><br /> private String getInput() {<br /> try {<br /> return in.readLine();<br /> }<br /> catch (IOException ex) {<br /> throw new RuntimeException("Couldn't perform operation", ex);<br /> }<br /> }<br /><br /> public void load(String path) {<br /> writeOut("open " + path);<br /> }<br /><br /> public void play() {<br /> writeOut("play");<br /> playing = true;<br /> paused = false;<br /> }<br /><br /> public void pause() {<br /> if(!paused) {<br /> writeOut("pause");<br /> playing = false;<br /> paused = true;<br /> }<br /> }<br /><br /> public void stop() {<br /> writeOut("stop");<br /> playing = false;<br /> paused = false;<br /> }<br /><br /> public boolean isPlayable() {<br /> writeOut("playable?");<br /> return Boolean.parseBoolean(getInput());<br /> }<br /><br /> public long getLength() {<br /> writeOut("length?");<br /> return Long.parseLong(getInput());<br /> }<br /><br /> public long getTime() {<br /> writeOut("time?");<br /> return Long.parseLong(getInput());<br /> }<br /><br /> public void setTime(long time) {<br /> writeOut("setTime " + time);<br /> }<br /><br /> public boolean getMute() {<br /> writeOut("mute?");<br /> return Boolean.parseBoolean(getInput());<br /> }<br /><br /> public void setMute(boolean mute) {<br /> writeOut("setMute " + mute);<br /> }<br /><br /> /**<br /> * Terminate the OutOfProcessPlayer. MUST be called before closing, otherwise<br /> * the player won't quit!<br /> */<br /> public void close() {<br /> if (open) {<br /> writeOut("close");<br /> playing = false;<br /> open = false;<br /> }<br /> }<br /><br /> /**<br /> * Determine whether the remote player is playing.<br /> * @return true if its playing, false otherwise.<br /> */<br /> public boolean isPlaying() {<br /> return playing;<br /> }<br /> <br /> /**<br /> * Determine whether the remote player is paused.<br /> * @return true if its paused, false otherwise.<br /> */<br /> public boolean isPaused() {<br /> return paused;<br /> }<br /> <br />}<br /></pre><br /><br />OutOfProcessPlayer.java:<br /><pre class="brush: java"><br />package org.quelea.video;<br /><br />import com.sun.jna.NativeLibrary;<br />import com.sun.jna.Pointer;<br />import java.awt.Canvas;<br />import java.io.BufferedReader;<br />import java.io.File;<br />import java.io.InputStreamReader;<br />import java.io.PrintStream;<br />import org.quelea.utils.QueleaProperties;<br />import uk.co.caprica.vlcj.binding.LibVlcFactory;<br />import uk.co.caprica.vlcj.binding.internal.libvlc_media_player_t;<br />import uk.co.caprica.vlcj.player.embedded.EmbeddedMediaPlayer;<br />import uk.co.caprica.vlcj.player.embedded.linux.LinuxEmbeddedMediaPlayer;<br />import uk.co.caprica.vlcj.player.embedded.mac.MacEmbeddedMediaPlayer;<br />import uk.co.caprica.vlcj.player.embedded.windows.WindowsEmbeddedMediaPlayer;<br />import uk.co.caprica.vlcj.runtime.RuntimeUtil;<br /><br />/**<br /> * Sits out of process so as not to crash the primary VM.<br /> * @author Michael<br /> */<br />public class OutOfProcessPlayer {<br /><br /> public OutOfProcessPlayer(final long canvasId) throws Exception {<br /><br /> //Lifted pretty much out of the VLCJ code<br /> EmbeddedMediaPlayer mediaPlayer;<br /> if (RuntimeUtil.isNix()) {<br /> mediaPlayer = new LinuxEmbeddedMediaPlayer(LibVlcFactory.factory().synchronise().log().create().libvlc_new(1, new String[]{"--no-video-title"}), null) {<br /><br /> @Override<br /> protected void nativeSetVideoSurface(libvlc_media_player_t mediaPlayerInstance, Canvas videoSurface) {<br /> libvlc.libvlc_media_player_set_xwindow(mediaPlayerInstance, (int) canvasId);<br /> }<br /> };<br /> }<br /> else if (RuntimeUtil.isWindows()) {<br /> mediaPlayer = new WindowsEmbeddedMediaPlayer(LibVlcFactory.factory().synchronise().log().create().libvlc_new(1, new String[]{"--no-video-title"}), null) {<br /><br /> @Override<br /> protected void nativeSetVideoSurface(libvlc_media_player_t mediaPlayerInstance, Canvas videoSurface) {<br /> Pointer ptr = Pointer.createConstant(canvasId);<br /> libvlc.libvlc_media_player_set_hwnd(mediaPlayerInstance, ptr);<br /> }<br /> };<br /> }<br /> else if (RuntimeUtil.isMac()) {<br /> mediaPlayer = new MacEmbeddedMediaPlayer(LibVlcFactory.factory().synchronise().log().create().libvlc_new(2, new String[]{"--no-video-title", "--vout=macosx"}), null) {<br /><br /> @Override<br /> protected void nativeSetVideoSurface(libvlc_media_player_t mediaPlayerInstance, Canvas videoSurface) {<br /> Pointer ptr = Pointer.createConstant(canvasId);<br /> libvlc.libvlc_media_player_set_nsobject(mediaPlayerInstance, ptr);<br /> }<br /> };<br /> }<br /> else {<br /> mediaPlayer = null;<br /> System.exit(1);<br /> }<br /><br /> mediaPlayer.setVideoSurface(new Canvas());<br /><br /> BufferedReader in = new BufferedReader(new InputStreamReader(System.in));<br /> String inputLine;<br /><br /> //Process the input - I know this isn't very OO but it works for now...<br /> while ((inputLine = in.readLine()) != null) {<br /> if (inputLine.startsWith("open ")) {<br /> inputLine = inputLine.substring("open ".length());<br /> mediaPlayer.prepareMedia(inputLine);<br /> }<br /> else if (inputLine.equalsIgnoreCase("play")) {<br /> mediaPlayer.play();<br /> }<br /> else if (inputLine.equalsIgnoreCase("pause")) {<br /> mediaPlayer.pause();<br /> }<br /> else if (inputLine.equalsIgnoreCase("stop")) {<br /> mediaPlayer.stop();<br /> }<br /> else if (inputLine.equalsIgnoreCase("playable?")) {<br /> System.out.println(mediaPlayer.isPlayable());<br /> }<br /> else if (inputLine.startsWith("setTime ")) {<br /> inputLine = inputLine.substring("setTime ".length());<br /> mediaPlayer.setTime(Long.parseLong(inputLine));<br /> }<br /> else if (inputLine.startsWith("setMute ")) {<br /> inputLine = inputLine.substring("setMute ".length());<br /> mediaPlayer.mute(Boolean.parseBoolean(inputLine));<br /> }<br /> else if (inputLine.equalsIgnoreCase("mute?")) {<br /> boolean mute = mediaPlayer.isMute();<br /> System.out.println(mute);<br /> }<br /> else if (inputLine.equalsIgnoreCase("length?")) {<br /> long length = mediaPlayer.getLength();<br /> System.out.println(length);<br /> }<br /> else if (inputLine.equalsIgnoreCase("time?")) {<br /> long time = mediaPlayer.getTime();<br /> System.out.println(time);<br /> }<br /> else if (inputLine.equalsIgnoreCase("close")) {<br /> System.exit(0);<br /> }<br /> else {<br /> System.out.println("unknown command: ." + inputLine + ".");<br /> }<br /> }<br /> }<br /><br /> public static void main(String[] args) {<br /> //Next 3 lines Quelea specific<br /> File nativeDir = new File("lib/native");<br /> NativeLibrary.addSearchPath("libvlc", nativeDir.getAbsolutePath());<br /> NativeLibrary.addSearchPath("vlc", nativeDir.getAbsolutePath()); <br /><br /> PrintStream stream = null;<br /> try {<br /> stream = new PrintStream(new File(QueleaProperties.get().getQueleaUserHome(), "ooplog.txt"));<br /> System.setErr(stream); //This is important, need to direct error stream somewhere<br /> new OutOfProcessPlayer(Integer.parseInt(args[0]));<br /> }<br /> catch (Exception ex) {<br /> ex.printStackTrace();<br /> }<br /> finally {<br /> stream.close();<br /> }<br /> }<br />}<br /></pre><br /><br />Hopefully, after you study the above two classes it should be pretty self-explanatory what's going on. The two classes talk to each other via the streams and react accordingly. The protocol there isn't complete - for now I've just completed what Quelea requires, though if I develop it into a library in its own right I'll complete the API obviously!<br /><br /><h2>Conclusion</h2><br />It's not trivial implementing out of process players in VLCJ, but neither is it ridiculously difficult and it does provide for trouble free playing if you get it right. The code above could definitely, and will almost definitely be improved - in terms of quality, completeness and error handling (there's nothing in there at the moment to cope with the external VM just disappearing or being closed externally, for instance.) And while communicating over streams like the above is reliable and doesn't need any big external libraries on top, it's not the fastest approach (more than good enough for my purpose however.)<br /><br />However, as I already stated the purpose of this is to get the idea across, to provide some skeleton code for out of process VLCJ players, and to show that with a bit of work, it's entirely possible to get excellent video support in a Java application (even if it is done natively.)<br /><br />I hope it proves useful to at least someone!Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com5tag:blogger.com,1999:blog-4169375346527088512.post-39353540405448027222011-07-31T14:46:00.000-07:002011-07-31T15:30:23.902-07:00Java and Video - part 2A while back I posted whining about the lack of decent video support in Java, and the fact that try as I might I just couldn't seem to find a decent one. I wrote this as part of my struggles trying to get video implemented properly in <a href="http://www.quelea.org">Quelea</a>. The good news is that the trunk version of Quelea seems to have decent, cross platform and reliable video that's compatible with any format you throw at it. And it's all thanks to the VLCJ / VLC guys. The bad news is that, well, it's not as obvious or as easy as it might first look.<br /><br />Before I go into details however, I think an honourable mention goes to <a href="http://www.xuggle.com/xuggler/">Xuggler</a>. It was the first package I really tried that seemed to be getting me somewhere - with really quite little effort after watching the tutorials I had a basic video application going that could play most file types. It couldn't do any seeking, pausing or the like but that I thought would be relatively easy. And, guess what - once again I was completely wrong. See, Xuggler isn't a high level video API. You don't point a video file and canvas at it and tell it to play one on the other. Instead, you have to open each packet in the file, check what stream it comes from, see if it's a video or an audio packet, deal with it appropriately and make sure the timestamps of the two match up (that's the really hard part)...<br /><br />Initial results looked positive. But before long the video and audio on the videos drifted slightly out of sync. If there was a significant pause on the video because it was fetching it from disk or something like that, this problem got worse still. There were all sorts of corner cases as well that would've taken months to track down and cope with, both in terms of streaming, file formats, sync issues and so on. The guys on the discuss page were very friendly, and there were excellent tutorials provided - but as time wore on it became clearer and clearer Xuggler wasn't going to be for me. Beyond a certain point you were always going to be on your own, and I needed to go way beyond that point!<br /><br />That's not to say Xuggler's useless, far from it. If you're doing any low level video stuff (transcoding, converting between formats, analysing frame / sound contents etc.) it's probably the best out there for Java. But that wasn't what I needed to do. The crux of the matter is, Xuggler just isn't really designed to be used in this way. It's not a high level video API, nor will it ever be.<br /><br />At this point I really did think all my options were out. JMF was dead, no-one on the face of this earth can seem to get FMJ working / it's half dead too, VLC/VLCJ kept bringing the VM down, Xuggler was aimed at the wrong area for me, and to top it off my JavaFX2 hopes were dashed by finding out video-wise it only supported FLV, at least for now.<br /><br />Rather than keep trying to find new things to use (I'd done a <span style="font-style:italic;">lot</span> of Googling by now!) I decided to revisit my options and see if there was anything I'd missed. On the JMF and FMJ front this seemed very unlikely. The conclusions I came too seem to be reflected by many others for very valid reasons - there's not a lot you can say about projects that genuinely seem dead and don't work properly. And as for JavaFX, well it really does just support FLV and I really do need much more than that! I'd already taken a long, hard look at Xuggler; this left me with VLCJ and more specifically asking the following two questions:<br /><br /><ul><br /><li>Why did it seem to crash the VM every so often whenever I did anything complicated?</li><br /><li>Could this be avoided?</li><br /></ul><br /><br />This didn't take too long to find out: http://code.google.com/p/vlcj/wiki/Crashes<br />In fact, I'd already found and read the link before, but I'd brushed it aside because it basically seemed to say that there wasn't much hope with ever getting the thing to work 100% reliably. At least not without resorting to some out of process magic which was a route I really didn't want to go down.<br /><br />After getting to this point though I did start to look down the out of process route, and it's this approach that I'm currently using in Quelea. In Windows it appears to work absolutely fine, I haven't managed to test anywhere else yet but in theory there's no reason why it shouldn't work on MacOS or Linux (potentially more native code might need to be executed on Mac to get it to work; this won't be an out of the box solution on Mac. But more on that later hopefully!)<br /><br />To avoid creating one huge post, the code and more details about it will follow. But if you've been after a run down of how to do video bits in Java and come unstuck, I hoped this has helped.<br /><br />In conclusion? If you want low level video manipulation, use Xuggler. If you want to play videos nicely in a variety of formats, use VLCJ with out of process players.Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0tag:blogger.com,1999:blog-4169375346527088512.post-44886201445204276342011-03-08T16:13:00.000-08:002011-04-04T14:35:48.961-07:00The invasion of "let" - tagletsI'm slowly coming to realise just how much Sun seemed to like the "let" suffix. We have applets, servlets, midlets, Xlets - and just as I think I'm running out, I discover doclets and taglets!<br /><br />It all started when, for our final year project, we decided to use the convention of an "@qa" annotation with a set format in the javadoc at the start of each class file to document who had QAed the file and when. It works well just as is (and this is how we were intending to leave it) but a part of me couldn't help but get slightly annoyed at the copious amounts of javadoc warnings this produced. I wondered if there was a way to not just shut javadoc up, but to sensibly extend it so it knew about this extra tag and could write out some meaningful information to the resulting HTML.<br /><br />Although this is rather poorly documented, it turns out it can, and it's really quite nice to use. Initial searching led me <a href="http://download.oracle.com/javase/1.4.2/docs/tooldocs/javadoc/taglet/overview.html">here</a>, which provides a brief overview about taglets (but enough to get started.) Essentially, you need to define a class that conforms to the com.sun.javadoc.Tag interface, create a static register method with a fixed definition, then inform Javadoc about the location of the taglet with the <code>-taglet</code> and <code>-tagletpath</code> parameters. The Java class you create is like any other and has access to the line, column and file that the tag is in as well as all its text. So you can easily integrate it with your main program if you wish!<br /><br />I would publish code, but since what I've done forms part of our final year project it could potentially open up plagarism disputes which isn't a road I want to go down! If I revisit it after my degree then I'll post up another example, along with code.<br /><br />In terms of integrating it with Netbeans, I had to override the Javadoc ant task to get it working properly (since it needs to compile the taglet and know the absolute path for the taglet path) but after that it worked very nicely indeed.<br /><br />Should I see the need for extra Javadoc tags again, I'll definitely think of this - documentation is poor so there's a through hoops to jump through first, but once you know how it really is pretty easy to set up and use.Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0tag:blogger.com,1999:blog-4169375346527088512.post-60733163320396163692011-01-21T14:20:00.000-08:002011-01-21T14:23:30.085-08:00And the solution...Well, this one wasn't the toughest by any stretch. But it does highlight some weird syntax that's possible (though highly frowned upon.)<br /><br />Rather than putting the square brackets that signal the return type is an array straight after the return type, these can also go after the method declaration. So it's exactly the same as:<br /><br /><pre><br />public class Test{<br /> public byte[][][] functionArray() {<br /> return null;<br /> }<br />}<br /></pre><br /><br />So yes, it compiles and runs fine.<br /><br />It's a bit of a weird design choice this one - these days it's just there for backwards compatibility, and even the JLS goes so far as to say it shouldn't be used in new code. So stay away from writing it, but if you see some weird old code like this then don't be too shocked :-)Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0tag:blogger.com,1999:blog-4169375346527088512.post-81599470949060543702011-01-20T18:55:00.000-08:002011-01-20T18:58:07.502-08:00You know you love them really...This is a quickie, answer will be posted in due course.<br /><br />Does this compile, and why / why not?<br /><br /><pre><br />public class Test{<br /> public byte functionArray()[][][] {<br /> return null;<br /> }<br />}<br /></pre>Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0tag:blogger.com,1999:blog-4169375346527088512.post-41112914788580877752011-01-12T17:49:00.000-08:002011-01-12T18:07:14.967-08:00Java and videoOne of the possible future features I've always had in mind for <a href="http://www.quelea.org">Quelea</a> is decent video support. The ability to import and display avi or mkv files, or to play a clip seamlessly from a DVD. This isn't really something I'd done previously, so I was interested to see the options available. All things considered, I thought that it's such a common thing to want to do there must be one or two good, well accepted libraries around at handling all of the media side of things.<br /><br />I was wrong. Very wrong!<br /><br />Considering that I want a cross platform way to do all the above, the amount of options available are considerably limited. The classic one is the JMF, which is probably the best option around. This doesn't mean it's good however - it's woefully outdated, the APIs aren't the best to work with in the world by a long shot and every other question about it asked online seems to end up with something along the lines of "wow, you're using JMF? Good luck..."!<br /><br />So, I decided to search elsewhere. But really, there's not much of an elsewhere. VLCJ looked like a promising lead but relying on native code that has a habit of completely breaking the VM every so often isn't good for much more than tech demos. FMJ looks good in theory, but no-one can seem to get it to work properly, me included. And JVLC (an earlier project than VLCJ) is full of bugs and completely dead to any kind of activity.<br /><br />Frantically, I turned to <a href="http://stackoverflow.com/questions/4669384/dealing-with-video-dvds-avi-mkv-in-java">stack overflow</a> for help to see if there was something obvious I'm missing. Seems not!<br /><br />There is however a glimmer of hope on the horizon. When JavaFX 2.0 is released it looks like it's going to change things dramatically - I for one really hope so. Check out this video at 2:05 for a really impressive video UI demo:<br />http://www.youtube.com/watch?v=UXSmJYFrulY<br /><br />That's due sometime later this year. Until then however, it looks like there's no real accepted, good way to deal with video in Java. If I can't wait for JavaFX I might give the JMF a crack... but based on other's experiences, I'm not expecting great things.Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0tag:blogger.com,1999:blog-4169375346527088512.post-16493730010095867112010-12-21T09:42:00.000-08:002010-12-21T09:54:06.551-08:00Order mattersEvery so often I come across these simple things that after thinking about them I kick myself for not thinking about and knowing sooner. Earlier today was one of those moments!<br /><br />It might seem natural, assumed, even obvious that for many method calls it doesn't actually matter what way around things go. So take <code>"asd".equals("dsa");</code> - does it ever make a difference?<br /><br />In this case, no - but what about if we introduce variables?<br /><pre><br />String str = "asd";<br />System.out.println(str.equals("dsa"));<br />System.out.println("dsa".equals(str));<br /></pre><br /><br />Again, no difference. Now let's be a bit sneaky however and swap str to null (you may see where this is going!)<br /><pre><br />String str = null;<br />System.out.println("dsa".equals(str));<br />System.out.println(str.equals("dsa"));<br /></pre><br />Ahah! This time we (somewhat obviously) get a NPE on the third line. But the second line executes without any problems at all (also rather obviously - we're not dereferencing null here!)<br /><br />Conclusion? When comparing strings, it makes much more sense to adopt a design pattern of putting the literal first rather than a variable name. If this is the case and if the variable happens to be null, then equals will just return false rather than causing an NPE. 99% of time this is the desired behaviour, unless you know your strings should never be null and want to be screamed at if they are. Even so, it'd probably make more sense in this case to put a separate check in rather than relying on the order of the values to give you an NPE - that could easily disappear when someone else comes along and reverses the order without realising it was intentionally the other way around :-)Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0tag:blogger.com,1999:blog-4169375346527088512.post-45565288058078503402010-12-04T06:18:00.001-08:002010-12-04T06:40:37.560-08:00The solutionYou might expect it to print out 104, "10" being the sum of the fields and then "4" being the number of fields. You would of course be wrong :-)<br /><br />If you run the following code you won't get a number out at all, you'll get an exception:<br /><br /><pre><br />Exception in thread "main" java.lang.IllegalArgumentException: Attempt to get test.Outer field "test.Outer$Inner.this$0" with illegal data type conversion to int<br /> at sun.reflect.UnsafeFieldAccessorImpl.newGetIllegalArgumentException(UnsafeFieldAccessorImpl.java:46)<br /> at sun.reflect.UnsafeFieldAccessorImpl.newGetIntIllegalArgumentException(UnsafeFieldAccessorImpl.java:111)<br /> at sun.reflect.UnsafeQualifiedObjectFieldAccessorImpl.getInt(UnsafeQualifiedObjectFieldAccessorImpl.java:41)<br /> at java.lang.reflect.Field.getInt(Field.java:499)<br /> at test.Outer.<init>(Outer.java:21)<br /> at test.Outer.main(Outer.java:29)<br /></pre><br /><br />The key here is actually down to an implementation detail of Java - specifically how inner classes have a reference to their outer classes. If we make the inner class static (static inner classes don't have a reference to their enclosing class) then the problem goes away and we get the expected output, 104. So what's happening here?<br /><br />It might help to think how a non-static inner class can always access its outer class. And it also might help if I tell you it's more along the realms of hackery than black magic! The key here is a field, this$0, that the compiler puts in every inner class. This is set by a constructor the compiler also puts in the inner class which is called when an object of this type is instantiated. It's an implementation detail, but quite a geeky and fun one to play around with. If you decompile the class files, you can actually see this extra field and constructor in plain daylight.<br /><br />I got the idea for this puzzle from http://www.javaspecialists.eu/archive/Issue062.html - this explains this implementation detail far more accurately if you want to take a look.<br /><br />So, we've got an extra field in the class that's of type Outer, not int. So when we try to call getInt() on this field, we understandably get an error.<br /><br />As a follow on puzzle which you should now be able to answer, if we change this line:<br /><br /><pre>catch(IllegalAccessException ex) {}</pre><br />to:<br /><pre>catch(Exception ex) {}</pre><br /><br />what's printed out now?<br /><br /><br />The answer? 105. The values that are ints are still summed, so we get 10, but there's still 5 fields in the Inner class!<br /><br />This is quite frankly a shoddy way to do things anyway, and the chances of doing something like this in real life are rather remote. But how could we fix it? One option is to check for synthetic fields and ignore them - synthetic fields are ones generated by the compiler and not by the user. So if we put <code>if(field.isSynthetic()) continue;</code> as the first line in the for loop and check for similar synthetic fields in counting them at the end, the problem goes away. There's a catch though - there's no requirement for the compiler to actually mark anything as synthetic. It's a bit like using a well established class in the sun package - while it might help in situations like this and is likely to be fine, it shouldn't really be relied upon.<br /><br />This may seem like a silly example, but it does serve a few lessons. First, as shown by the follow on puzzle, if you just catch all exception types and expect everything to work, you may well get caught out by bugs you weren't expecting, but can really bite you later. Secondly, don't use reflection to loop through fields. It's just plain wrong. If you really <span style="font-style:italic;">have</span> to, at least check for synthetic fields and make sure the fields are of the type you expect. And lastly, decompiling compiled code and looking at the results can be rather interesting in uncovering some Java implementation details!Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0tag:blogger.com,1999:blog-4169375346527088512.post-32345555599458948122010-11-17T03:16:00.000-08:002010-11-17T03:17:51.567-08:00No, I'm still not keeping these to myself ;-)The usual drill here - what does it print and why?<br /><br />...yes, it does compile :-)<br /><br /><pre><br />import java.lang.reflect.Field;<br /><br />public class Outer {<br /><br /> private class Inner {<br /> private int field1 = 1;<br /> private int field2 = 2;<br /> private int field3 = 3;<br /> private int field4 = 4;<br /> }<br /><br /> public Outer() {<br /> int count=0;<br /> Inner inner = new Inner();<br /> for(Field field : Inner.class.getDeclaredFields()) {<br /> field.setAccessible(true);<br /> try {<br /> count += field.getInt(inner);<br /> }<br /> catch(IllegalAccessException ex) {}<br /> }<br /> System.out.print(count);<br /> }<br /><br /> public static void main(String[] arguments) {<br /> new Outer();<br /> System.out.println(Inner.class.getDeclaredFields().length);<br /> }<br />}<br /></pre>Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0tag:blogger.com,1999:blog-4169375346527088512.post-30868665676424836112010-10-19T16:49:00.000-07:002010-10-19T16:57:58.951-07:00QueleaSo, I've started a new project on the side. It's something I've had on my mind for a while but something that I've never really had the incentive to start. Until now that is!<br /><br />The project is essentially a lyrics projection system designed for use in churches, much like OpenLP, Opensong and Easyworship. All of the preceding are decent packages and I've used them all, but there's various things in each of them I like and don't like. The aim? To bring all the features I do like together into one neat, easy to use package that's comprehensive, reliable and open source. When I say "I" I actually mean a few people I've spoken to as well as myself - I'm definitely getting other input before ploughing ahead! :-)<br /><br />The site's www.quelea.org - there's not much there at the moment but things are definitely on the way. (You may notice a few interesting things on there, I don't like to take everything quite 100% seriously!) I'm hoping by Christmas this year I'll have a bare bones working release to ship as an early alpha, and perhaps a few partially complete demos before then.<br /><br />Technical details? It's written in Java, uses hsqldb for its song database and uses substance for its look and feel (might as well have something that looks nice when you get it for free! Definitely better than the default look and feel...) Swing can look pretty. Honest :-) Beyond that there's not an awful lot decided yet, though the schedule file format will probably be a zip file stuffed with XML files and resources required (vaguely like the new MS Office formats.)<br /><br />Watch this space!Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0tag:blogger.com,1999:blog-4169375346527088512.post-51721875685625076602010-10-19T16:43:00.000-07:002010-10-19T16:47:04.627-07:00ListModel turns generic - at lastThe features of project coin have been shouted about and argued over for a while now, and other various API changes have also made it into the spotlight - a fork-join framework in java.util.concurrent and the ability to have shaped windows for instance.<br /><br />However, one thing that seems to have slipped under the spotlight is the fact that ListModel is now generic, a change that's well overdue in my opinion.<br /><br />http://download-llnw.oracle.com/javase/7/docs/api/javax/swing/ListModel.html<br /><br />Dealing with constant casting on list models thus far hasn't been nice, and I'm not sure why it's taken this long for the class to become generic - it's an obvious candidate and I struggle to see how it slipped under the carpet for both Java 5 and 6.<br /><br />It's a relatively minor change in the grand scheme of things, but sometimes it's the minor changes that actually turn out to be the ones that work best and save most time. I'll certainly look forward to the day when I no longer need to cast everything coming out of listmodels!Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0tag:blogger.com,1999:blog-4169375346527088512.post-15932954198683927482010-10-05T17:20:00.000-07:002010-10-05T17:25:56.583-07:00ApricotsNo, I haven't become addicted to the orange fruit growing on trees. But I have become a tad addicted to the game called apricots (again, nothing to do with the fruit and aside from "various reasons" I can't find any explanation as to why it's called this!)<br /><br />It's a game where you have to fly a little plane around and shoot enemy bases whilst avoiding civilian houses, trees and skyscrapers. Grab it, it's free and has Linux and Windows versions: http://www.fishies.org.uk/apricots.html<br /><br />It's rather simplistic, but highly addictive and I like the idea. Despite the fact the site claims it's still being developed, the last update was the best part of a decade ago, and after attempting to contact the author a while back and receiving no reply I'm pretty certain it's been abandoned. I'm considering taking the idea and rewriting it in Java with more modern looking graphics and gameplay - it'd be an interesting project on the side. That is if I can stop playing the thing...Michael Berryhttp://www.blogger.com/profile/13494814769553033799noreply@blogger.com0