Tunlr enable/disable script for Microsoft Surface

I recently bought a Microsoft Surface and I have been wanting to watch a few programmes on BBC iPlayer whilst out of the country for Christmas. I discovered Tunlr – a free media proxy service which allows access to Hulu and iPlayer regardless of geolocation. However, editing DNS server settings by hand is time consuming and awkward without using the trackpad, so I wanted a script to automate the task. This will also work for other proxy services such as unblock-us.com – just replace the DNS IPs in the script. I had previously written a quick script for changing IP configuration which used netsh commands but these don’t work on Windows RT. Some other PowerShell methods I found weren’t supported either but I did find new network settings cmdlets for the purpose that were added in Windows 8/RT.

The next problem was elevation to get sufficient rights to change the network settings. It transpires that the PowerShell and VBScript environments are heavily restricted in Windows RT, which prevents auto-prompting for elevation. Fortunately Windows RT does allow Run as Administrator from the right-click menu for .cmd scripts. If you’re using touch control, you just touch and hold then release for the right-click. The script will remind you if you forget to do this. Hover your mouse over the top right corner of the script below, and use the View Source button to save the following to your desktop as Tunlr.cmd:

@echo off

::Use Tunlr to watch streaming TV services regardless of geolocation
::Tunlr DNS servers redirect requests for well-known services via Tunlr's proxy servers
::Tunlr should only be used while watching streams to reduce server load
::More details at http://tunlr.net/

::Elevation cannot be automated on Windows RT since object creation is disabled for PowerShell and VBScript
::http://stackoverflow.com/questions/13504499/windows-rt-powershell-permissiondenied-on-new-object

ipconfig /all | find "142.54.177.158" > nul && (
  echo Disabling Tunlr...
  PowerShell -Command Set-DnsClientServerAddress -InterfaceAlias "WiFi" -ResetServerAddresses || (
    echo Right-click and re-run this script as Administrator
    pause
  )
) || (
  echo Enabling Tunlr...
  PowerShell -Command Set-DnsClientServerAddress -InterfaceAlias "WiFi" -ServerAddresses 142.54.177.158,209.141.56.79 || (
    echo Right-click and re-run this script as Administrator
    pause
  )
)

UAC elevation for Windows batch script

I recently needed to make an interactive batch script elevate for admin privileges. I found an example script by jagaroth, and then refined it to make it even more compact. It only writes out one temporary script file, and passes the rest of the required variables on the command line. It can cope with paths containing spaces. It was something of a shell escaping nightmare as you can see from line 14!

@echo off

::Windows XP doesn't have UAC so skip
for /f "tokens=3*" %%i in ('reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v ProductName ^| Find "ProductName"') do set WINVER=%%i %%j 
echo %WINVER% | find "XP" > nul && goto commands

::prompt for elevation
if "%1" == "UAC" goto elevation
(
  echo Set objShell = CreateObject^("Shell.Application"^)
  echo Set objFSO = CreateObject^("Scripting.FileSystemObject"^)
  echo strPath = objFSO.GetParentFolderName^(WScript.ScriptFullName^)
  echo If objFSO.FileExists^("%~0"^) Then
  echo   objShell.ShellExecute "cmd.exe", "/c """"%~0"" UAC ""%~dp0""""", "", "runas", 1
  echo Else
  echo   MsgBox "Script file not found"
  echo End If
) > "%TEMP%\UAC.vbs"
cscript //nologo "%TEMP%\UAC.vbs"
goto :eof
:elevation
del /q "%TEMP%\UAC.vbs"

:commands
::navigate back to this script's home folder
%~d2
cd "%~p2"

::put your main script here
echo 1st arg: %1
echo 2nd arg: %2
pause

Cross-compiling FFmpeg for Serviio with shared libraries on Synology NAS (for 7 CPU architectures)

This method will build FFmpeg 2.7.1 with the shared libraries (linked with relative paths) required for Serviio DLNA media server, using a Ubuntu/Xubuntu Desktop 14.04 virtual machine, however the same method probably works with many other Linux distributions. The target CPU architectures are ARMv5, ARMv7, ARM hard float ABI with NEON, Intel i686, Intel x64, and PowerPC e500v2.

The >10MB size of the static executable is kind of getting out of hand especially on embedded systems with very limited RAM. Using shared libs means many concurrent instances of FFmpeg can use broadly the same memory footprint, and package distribution binaries can be smaller – our target systems already have libmp3lame, libm, libz, librt, libgmp, libssl, libcrypto, libfreetype, libexpat, and libpthread which add up to several megabytes. I’m also guessing that the OS will decide when to unload them from RAM which could help when FFmpeg is being launched repeatedly in a short period of time (e.g. during library scanning).

The notes below also detail how to compile the Synology fork of FFmpeg 2.0.2 with Intel Evansport SMD (Streaming Media Drivers) hardware transcoding support. This source is itself based upon a fork by Intel which seems to form part of the Evansport SDK. It was not straightforward to compile, and now that I have released Serviio for Synology with hardware transcoding support I’m sharing my method in the hope it can help others. This older FFmpeg needs a patch to fix a long standing bug with DTS audio remuxing (reported by me) where the stream ID was incorrectly set:

--- ffmpeg-2.2.1/libavformat/mpegts.h
+++ ffmpeg-2.2.1-ticket1398/libavformat/mpegts.h
@@ -57,7 +57,8 @@
 #define STREAM_TYPE_VIDEO_DIRAC     0xd1
 
 #define STREAM_TYPE_AUDIO_AC3       0x81
-#define STREAM_TYPE_AUDIO_DTS       0x8a
+#define STREAM_TYPE_AUDIO_DTS       0x82
+#define STREAM_TYPE_AUDIO_TRUEHD    0x83
 
 typedef struct MpegTSContext MpegTSContext;
 
--- ffmpeg-2.2.1/libavformat/mpegtsenc.c
+++ ffmpeg-2.2.1-ticket1398/libavformat/mpegtsenc.c
@@ -311,6 +311,12 @@
         case AV_CODEC_ID_AC3:
             stream_type = STREAM_TYPE_AUDIO_AC3;
             break;
+        case AV_CODEC_ID_DTS:
+            stream_type = STREAM_TYPE_AUDIO_DTS;
+            break;
+        case AV_CODEC_ID_TRUEHD:
+            stream_type = STREAM_TYPE_AUDIO_TRUEHD;
+            break;
         default:
             stream_type = STREAM_TYPE_PRIVATE_DATA;
             break;

Although the unmodified FFmpeg source code will compile successfully for PowerPC CPUs, the FFmpeg binary will core dump when running any command on a video file. I contacted Synology Support for help with this issue back in October 2012 and was given a patch to use at that time which changes the use of a CPU register in the DSP assembly code. The FFmpeg source code has changed a bit recently meaning the patch was no longer valid, but I was able to discover the appropriate location in the code and create a new working patch (code was moved from dsputil_ppc.c to blockdsp.c):

diff -rupN ffmpeg/libavcodec/ppc/blockdsp.c ffmpeg-patched/libavcodec/ppc/blockdsp.c
--- ffmpeg/libavcodec/ppc/blockdsp.c	2015-06-19 21:44:40.000000000 +0100
+++ ffmpeg-patched/libavcodec/ppc/blockdsp.c	2015-07-11 04:06:27.326911100 +0100
@@ -115,8 +115,9 @@ static long check_dcbzl_effect(void)
     memset(fakedata, 0xFF, 1024);
 
     /* Below the constraint "b" seems to mean "address base register"
-     * in gcc-3.3 / RS/6000 speaks. Seems to avoid using r0, so.... */
-    __asm__ volatile ("dcbzl %0, %1" :: "b" (fakedata_middle), "r" (zero));
+     * in gcc-3.3 / RS/6000 speaks. Seems to avoid using r0, so.... 
+    __asm__ volatile ("dcbzl %0, %1" :: "b" (fakedata_middle), "r" (zero)); */
+    __asm__ volatile ("dcbz %0, %1" :: "r" (fakedata_middle), "r" (zero));
 
     for (i = 0; i < 1024; i++)
         if (fakedata[i] == (char) 0)
 
#-----set up Synology toolchain
cd ~/Downloads
#export DL_PATH="http://sourceforge.net/projects/dsgpl/files/DSM%205.0%20Tool%20Chains"
#export DL_PATH="http://sourceforge.net/projects/dsgpl/files/DSM%20Beta%205.2%20Tool%20Chains"
export DL_PATH="http://sourceforge.net/projects/dsgpl/files/DSM%205.2%20Tool%20Chains"
#-----add 32bit binary compatibility to Ubuntu 14.04 LTS (Syno toolchains are 32bit)
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386
sudo apt-get install git
sudo apt-get install chrpath
#----------------------------------------------------
#-----NOW PASTE ONE OF THE FOLLOWING SIX BLOCKS TO THE TERMINAL, DEPENDING ON YOUR TARGET CPU TYPE
#-----Marvell Kirkwood mv6281/mv6282 (ARMv5te)
#-----Marvell Armada 370/375/XP (ARMv7l FPU)
#-----Mindspeed Comcerto 2000/Marvell Armada 375 (ARM Cortex-A9 NEON)
#-----Freescale PowerQUICC III MPC8533E/QorIQ P1022 (PPC e500v2 FPU)
#-----Intel Atom/Bromolow/Cedarview (i686 SSSE3)
#-----Intel Evansport (i686 SSSE3 various HW decoders, HW H.264 encoder)




#-----Marvell Kirkwood mv6281/mv6282 CPU is based on the ARMv5TE core which has DSP and Thumb instruction support
#-----However using Thumb on ARMv5 results in worse performance
#-----http://www.arm.com/products/processors/technologies/dsp-simd.php
#-----http://www.marvell.com/embedded-processors/kirkwood/assets/88f6282-3_pb.pdf
wget "${DL_PATH}/Marvell%2088F628x%20Linux%202.6.32/6281-gcc464_glibc215_88f6281-GPL.txz"
tar xvJf 6281-gcc464_glibc215_88f6281-GPL.txz
#-----Marvell gave the ARMv5 and ARMv7 toolchains the same name so rename to allow concurrent installations
mv arm-marvell-linux-gnueabi/ arm-none-linux-gnueabi/
#-----these next two packages are needed to build libshine
sudo apt-get install automake
sudo apt-get install libtool
export CROSS_PREFIX=arm-marvell-linux-gnueabi
export TOOLCHAIN=/usr/local/arm-none-linux-gnueabi
export TARGET=arm-marvell-linux-gnueabi
export MARCH="-march=armv5te -mtune=marvell-f -mtune=xscale"
export LAME_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static --disable-decoder"
#-----libx264:
#-----http://forum.doom9.org/showthread.php?t=160584
#-----https://mailman.videolan.org/pipermail/x264-devel/2010-December/008093.html
#-----x264 builds with NEON by default because x264 is so slow without NEON (and on any non-NEON chip) as to be useless.
#-----You can of course compile with --disable-asm on such chips, but we don't do it by default because
#-----we don't feel it's necessary to actively support chips on which x264 would be basically useless
export X264_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --enable-shared --disable-asm --enable-pic --enable-strip"
#-----FFmpeg:
#-----ARMv5TE CPU-specific options (based on inspecting 'configure' script https://github.com/FFmpeg/FFmpeg/blob/master/configure)
#-----pkg-config needs to be defined because with cross-prefix it assumes ${CROSS_PREFIX}-pkg-config which doesn't exist, and then librtmp won't be detected
#-----something changed in the FFmpeg configure script and now vfp and neon optimizations have to be explicitly disabled for ARMv5TE, which doesn't have those features in any case
export FF_CONFIG="./configure --arch=arm --cpu=armv5te --enable-cross-compile --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --target-os=linux --prefix=${TOOLCHAIN} --enable-shared --disable-static --enable-pic --disable-ffplay --disable-ffserver --disable-neon --disable-vfp --disable-armv6 --disable-armv6t2 --disable-debug --enable-pthreads --enable-libshine --enable-librtmp --enable-libass --enable-libspeex --enable-gpl --enable-libx264 --enable-gnutls --pkg-config=pkg-config --extra-version=compiled_by_patters_for_Serviio"
#export FF_CONFIG="./configure --arch=arm --cpu=armv5te --enable-cross-compile --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --target-os=linux --prefix=${TOOLCHAIN} --enable-shared --disable-static --enable-pic --disable-ffplay --disable-ffserver --disable-neon --disable-vfp --disable-armv6 --disable-armv6t2 --disable-debug --enable-pthreads --enable-libshine --enable-librtmp --enable-libass --enable-libspeex --enable-gpl --enable-libx264 --enable-gnutls --enable-nonfree --enable-libfdk_aac --pkg-config=pkg-config --extra-version=compiled_by_patters_for_Serviio"
wget https://github.com/savonet/shine/zipball/master
unzip master
cd toots-shine-*
./bootstrap
cd ..
#-----fetch libfdk-aac fixed point math AAC encoder, binaries linked to it cannot be redistributed under GPL
#-----so it is excluded from the FFmpeg binary I built for the Serviio package
wget http://sourceforge.net/projects/opencore-amr/files/fdk-aac/fdk-aac-0.1.4.tar.gz
tar xvzf fdk-aac-0.1.4.tar.gz
cd fdk-aac-0.1.4
./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static
make
make install
cd ..
 



#-----Marvell Armada 370/XP CPU is based on a dual issue ARMv7 core with Thumb-2, VFPv3-16, but no NEON vector unit
#-----http://www.marvell.com/embedded-processors/armada-300/assets/Marvell_ARMADA_370_SoC.pdf
#-----http://www.arm.com/products/processors/technologies/vector-floating-point.php
#-----Marvell Armada 375 CPU is based on dual ARM Cortex-A9 cores with NEON vector unit, but there is no Synology toolchain with NEON support
#-----Synology seems to have used this toolchain to build DSM for the Armada 375
#-----http://www.marvell.com/embedded-processors/armada-300/assets/ARMADA_375_SoC-01_product_brief.pdf
wget "${DL_PATH}/Marvell%20Armada%20370%20Linux%203.2.40/armada370-gcc464_glibc215_hard_armada-GPL.txz"
tar xvJf armada370-gcc464_glibc215_hard_armada-GPL.txz
export CROSS_PREFIX=arm-marvell-linux-gnueabi
export TARGET=arm-marvell-linux-gnueabi
export TOOLCHAIN=/usr/local/${CROSS_PREFIX}
#-----Tune for Marvell PJ4 dual issue core (two instructions per clock)
#-----Thumb-2 can be used on ARMv6 or newer with no performance drop
export MARCH="-march=armv7-a -mcpu=marvell-pj4 -mtune=marvell-pj4 -mhard-float -mfpu=vfpv3-d16 -mthumb"
export LAME_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static --disable-decoder"
#-----libx264:
#-----http://forum.doom9.org/showthread.php?t=160584
#-----https://mailman.videolan.org/pipermail/x264-devel/2010-December/008093.html
#-----x264 builds with NEON by default because x264 is so slow without NEON (and on any non-NEON chip) as to be useless.
#-----You can of course compile with --disable-asm on such chips, but we don't do it by default because
#-----we don't feel it's necessary to actively support chips on which x264 would be basically useless
#-----http://lists.busybox.net/pipermail/buildroot/2014-October/107797.html
export X264_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --enable-shared --disable-asm --enable-pic --enable-strip"
#-----FFmpeg:
#-----ARMv7 CPU-specific options (based on inspecting 'configure' script https://github.com/FFmpeg/FFmpeg/blob/master/configure)
#-----pkg-config needs to be defined because with cross-prefix it assumes ${CROSS_PREFIX}-pkg-config which doesn't exist, and then librtmp won't be detected
export FF_CONFIG="./configure --arch=arm --cpu=armv7-a --enable-thumb --enable-cross-compile --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --target-os=linux --prefix=${TOOLCHAIN} --enable-shared --disable-static --enable-pic --disable-ffplay --disable-ffserver --disable-neon --disable-debug --enable-pthreads --enable-libmp3lame --enable-librtmp --enable-libass --enable-libspeex --enable-gpl --enable-libx264 --enable-gnutls --pkg-config=pkg-config --extra-version=compiled_by_patters_for_Serviio"




#-----Mindspeed Comcerto 2000 CPU is based on dual ARM Cortex-A9 cores with NEON vector unit
#-----http://www.mindspeed.com/products/cpe-processors/comcertoreg-2000
#-----http://www.arm.com/products/processors/cortex-a/cortex-a9.php
#-----http://www.arm.com/products/processors/technologies/neon.php
#-----Armada 375 toolchain uses glibc 2.15, Comcerto 2000 uses glibc 2.17 
wget "${DL_PATH}/Mindspeed%20Comcerto%202000%20Linux%203.2.40/comcerto2k-gcc464_glibc217_neon_comcerto-GPL.txz"
tar xvJf comcerto2k-gcc464_glibc217_neon_comcerto-GPL.txz
export CROSS_PREFIX=arm-cortexa9-linux-gnueabi
export TARGET=arm-cortexa9-linux-gnueabi
export TOOLCHAIN=/usr/local/${CROSS_PREFIX}
#-----it seems that in general neon should be used as the fpu when present unless there's a specific reason not to use it
export MARCH="-march=armv7-a -mcpu=cortex-a9 -mfpu=neon -mhard-float -mthumb"
export LAME_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static --disable-decoder"
#-----libx264:
export X264_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --enable-shared --enable-pic --enable-strip"
#-----FFmpeg:
#-----ARMv7 CPU-specific options (based on inspecting 'configure' script https://github.com/FFmpeg/FFmpeg/blob/master/configure)
#-----pkg-config needs to be defined because with cross-prefix it assumes ${CROSS_PREFIX}-pkg-config which doesn't exist, and then librtmp won't be detected
export FF_CONFIG="./configure --arch=arm --cpu=cortex-a9 --enable-thumb --enable-cross-compile --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --target-os=linux --prefix=${TOOLCHAIN} --enable-shared --disable-static --enable-pic --disable-ffplay --disable-ffserver --disable-debug --enable-pthreads --enable-libmp3lame --enable-librtmp --enable-libass --enable-libspeex --enable-gpl --enable-libx264 --enable-gnutls --pkg-config=pkg-config --extra-version=compiled_by_patters_for_Serviio"




#-----Freescale PowerQUICC III MPC8533E/QorIQ P1022 CPUs use the PowerPC e500v2 core with Signal Processing Engine (SPE) which is not a classic FPU design, but have no AltiVec vector unit 
#-----Some QorIQ models have e500mc cores with true FPUs but these are not used in 2013 series Synology NAS
#-----http://en.wikipedia.org/wiki/QorIQ
#-----http://cache.freescale.com/files/32bit/doc/fact_sheet/QP1022FS.pdf?fpsp=1
wget "${DL_PATH}/PowerPC%20QorIQ%20Linux%202.6.32/qoriq-gcc4374_eglibc2874_qoriq-GPL.txz"
tar xvJf qoriq-gcc4374_eglibc2874_qoriq-GPL.txz
export CROSS_PREFIX=powerpc-none-linux-gnuspe
export TOOLCHAIN=/usr/local/${CROSS_PREFIX}
export TARGET=powerpc-none-linux-gnuspe
export MARCH="-mcpu=8548 -mhard-float -mfloat-gprs=double"
export LAME_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static --disable-decoder"
#-----asm disabled since QorIQ has no AltiVec
export X264_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --enable-shared --disable-asm --enable-pic --enable-strip"
#-----FFmpeg:#-----PowerPC e500v2 CPU-specific options (based on inspecting 'configure' script https://github.com/FFmpeg/FFmpeg/blob/master/configure)
#-----pkg-config needs to be defined because with cross-prefix it assumes ${CROSS_PREFIX}-pkg-config which doesn't exist, and then librtmp won't be detected
export FF_CONFIG="./configure --arch=ppc --cpu=e500v2 --enable-cross-compile --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --target-os=linux --prefix=${TOOLCHAIN} --enable-shared --disable-static --enable-pic --disable-ffplay --disable-ffserver --disable-debug --enable-pthreads --enable-libmp3lame --enable-librtmp --enable-libass --enable-libspeex --enable-gpl --enable-libx264 --enable-gnutls --pkg-config=pkg-config --extra-version=compiled_by_patters_for_Serviio"




#-----With the exception of Evansport (i686), Intel CPUs used in Synology products are all 64bit and SSSE3 capable but existing DSM libs are 32bit at time of writing (DSM 4.1), so use the i686 toolchain rather than x86_64
#-----libx264 requires yasm 1.20
sudo apt-get install yasm
#-----unlike the others this toolchain has an external dependency on libz
sudo apt-get install libz1:i386
wget "${DL_PATH}/Intel%20x86%20Linux%203.2.40%20%28Pineview%29/x64-gcc463_glibc213_i686_patched-GPL.txz"
tar xvJf x64-gcc463_glibc213_i686_patched-GPL.txz
export CROSS_PREFIX=i686-pc-linux-gnu
export TARGET=i686-pc-linux-gnu
export TOOLCHAIN=/usr/local/${CROSS_PREFIX}
#-----We don't really care how lame is built since ffmpeg will use the version in DSM.
#-----The DSM version is built without asm optimizations but these only make a 5% difference, and have been known to introduce instability
export LAME_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static --disable-decoder"
export X264_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --enable-shared --enable-pic --enable-strip"
#-----FFmpeg:
#-----Intel CPU-specific options (based on inspecting 'configure' script https://github.com/FFmpeg/FFmpeg/blob/master/configure)
#-----pkg-config needs to be defined because with cross-prefix it assumes ${CROSS_PREFIX}-pkg-config which doesn't exist, and then librtmp won't be detected
export FF_CONFIG="./configure --arch=x86 --enable-cross-compile --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --target-os=linux --prefix=${TOOLCHAIN} --enable-shared --disable-static --enable-pic --disable-ffplay --disable-ffserver --disable-debug --enable-pthreads --enable-libmp3lame --enable-librtmp --enable-libass --enable-libspeex --enable-gpl --enable-libx264 --enable-gnutls --pkg-config=pkg-config --extra-version=compiled_by_patters_for_Serviio"




#-----Intel Evansport with support for hardware decoding of VC-1, H.264, MPEG-4, MPEG2, AAC and hardware encoding of H.264
#-----http://www.anandtech.com/show/8020/synology-ds214play-intel-evansport-almost-done-right/9
#-----http://ukdl.synology.com/download/spk/VideoStation/1.3-0547/
#-----libx264 requires yasm 1.20
sudo apt-get install yasm
wget "${DL_PATH}/Intel%20x86%20Linux%203.2.40%20%28Evansport%29/evansport-gcc463_glibc213_i686_patched-GPL.txz"
tar xvJf evansport-gcc463_glibc213_i686_patched-GPL.txz
#-----Both Intel toolchains the same name so rename to allow concurrent installations
mv i686-pc-linux-gnu evansport-pc-linux-gnu
#-----Synology headers and libs for the Intel Streaming Media Driver support
wget http://sourceforge.net/projects/dsgpl/files/Synology%20NAS%20GPL%20Source/5565branch/evansport-chroot.txz
mkdir evansport-chroot
cd evansport-chroot
tar xvJf ../evansport-chroot.txz
sudo cp -R lib/intelce-utilities/ ../evansport-pc-linux-gnu/lib
sudo cp -R include/intelce-utilities/ ../evansport-pc-linux-gnu/include
cd ..
#end of Intel SMD section
export CROSS_PREFIX=i686-pc-linux-gnu
export TARGET=i686-pc-linux-gnu
export MARCH="-march=atom"
export TOOLCHAIN=/usr/local/evansport-pc-linux-gnu
export LAME_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static --disable-decoder"
export X264_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --enable-shared --enable-pic --enable-strip"
#-----FFmpeg:
#-----Intel CPU-specific options (based on inspecting 'configure' script https://github.com/FFmpeg/FFmpeg/blob/master/configure)
#-----pkg-config needs to be defined because with cross-prefix it assumes ${CROSS_PREFIX}-pkg-config which doesn't exist, and then librtmp won't be detected
export FF_CONFIG="./configure.syno --arch=x86 --cpu=atom --enable-cross-compile --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --target-os=linux --prefix=${TOOLCHAIN} --enable-shared --disable-static --enable-pic --disable-ffplay --disable-ffserver --disable-debug --enable-pthreads --enable-libmp3lame --enable-librtmp --enable-libass --enable-libspeex --enable-libx264 --enable-libh264_smd --enable-smd --enable-gpl --enable-gnutls --pkg-config=pkg-config --extra-version=SMD_enabled_with_DTS_remux_fix-compiled_by_patters_for_Serviio"
#-----implicit function declaration issue for libavcodec/ismd_ffmpeg_audio_util.o, libavcodec/libx264.o, ffmpeg.o
#-----http://stackoverflow.com/questions/11561261/cc1-warnings-being-treated-as-errors
#-----http://stackoverflow.com/questions/13986301/is-gcc-doing-implicit-function-declarations-incorrectly-in-c99-mode
#-----how to workaround an error using make V=1 (re-issue gcc command, removing -Werror=implicit-function-declaration from command line)
#-----http://trac.ffmpeg.org/ticket/1794
#-----ffmpeg_g and ffprobe_g libavcodec/libavcodec.so: undefined reference to `json_object_array_length' used by (libavcodec/syno_trans_loading.c)
#-----add -ljson-c to the linker parameters




#-----Intel Core-i3 CPUs are 64bit and SSSE3 and seem to have their DSM built using x86_64
#-----libx264 requires yasm 1.20
sudo apt-get install yasm
wget "${DL_PATH}/Intel%20x86%20Linux%203.2.40%20%28Pineview%29/x64-gcc473_glibc217_x86_64-GPL.txz"
tar xvJf x64-gcc473_glibc217_x86_64-GPL.txz
export CROSS_PREFIX=x86_64-pc-linux-gnu
export TARGET=x86_64-pc-linux-gnu
export TOOLCHAIN=/usr/local/${CROSS_PREFIX}
#-----We don't really care how lame is built since ffmpeg will use the version in DSM.
#-----The DSM version is built without asm optimizations but these only make a 5% difference, and have been known to introduce instability
export LAME_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static --disable-decoder"
export X264_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --enable-shared --enable-pic --enable-strip"
#-----FFmpeg:
#-----Intel CPU-specific options (based on inspecting 'configure' script https://github.com/FFmpeg/FFmpeg/blob/master/configure)
#-----pkg-config needs to be defined because with cross-prefix it assumes ${CROSS_PREFIX}-pkg-config which doesn't exist, and then librtmp won't be detected
export FF_CONFIG="./configure --arch=x86 --enable-cross-compile --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --target-os=linux --prefix=${TOOLCHAIN} --enable-shared --disable-static --enable-pic --disable-ffplay --disable-ffserver --disable-debug --enable-pthreads --enable-libmp3lame --enable-librtmp --enable-libass --enable-libspeex --enable-gpl --enable-libx264 --enable-gnutls --pkg-config=pkg-config --extra-version=compiled_by_patters_for_Serviio"





#----------------------------------------------------
#-----script continues
sudo mv `echo $TOOLCHAIN | sed -r "s%^.*/(.*$)%\1%"` /usr/local
#-----some of the toolchains have bad permissions
sudo chmod -R 0755 ${TOOLCHAIN}
export PATH=${TOOLCHAIN}/bin:$PATH
export AR=${TOOLCHAIN}/bin/${CROSS_PREFIX}-ar
export AS=${TOOLCHAIN}/bin/${CROSS_PREFIX}-as
export CC=${TOOLCHAIN}/bin/${CROSS_PREFIX}-gcc
export CXX=${TOOLCHAIN}/bin/${CROSS_PREFIX}-g++
export LD=${TOOLCHAIN}/bin/${CROSS_PREFIX}-ld
export LDSHARED="${TOOLCHAIN}/bin/${CROSS_PREFIX}-gcc -shared "
export RANLIB=${TOOLCHAIN}/bin/${CROSS_PREFIX}-ranlib
export CFLAGS="-I${TOOLCHAIN}/include -O3 ${MARCH}"
export LDFLAGS="-L${TOOLCHAIN}/lib"
export PKG_CONFIG_PATH="${TOOLCHAIN}/lib/pkgconfig"


#-----fetch Synology's DSM 5565 sources for certain libraries we want to link against
wget https://dl.dropboxusercontent.com/u/1188556/DSM5565-src.tar.xz
tar xvJf DSM5565-src.tar.xz

#-----libz shared lib (toolchain only has static lib)
cd zlib-1.x
./configure --prefix=${TOOLCHAIN}
#-----owing to a bug in the configure script, libz cross compiles without a SONAME (check it with objdump -x)
#-----http://forums.gentoo.org/viewtopic-p-7156392.html
sed -i "s/\(^LDSHARED=.*$\)/\1 -Wl,-soname,libz.so.1,--version-script,zlib.map/" Makefile
make
make install
cd ..

#-----LAME shared lib
cd lame-398
$LAME_CONFIG
make
make install
cd ..

#-----expat shared lib
cd expat-2.x
./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static
make
make install
cd ..

#-----freetype shared lib
cd freetype-2.x
#-----disable external zlib library since it appears Synology compiled DSM-included libfreetype binary this way
#-----failure to do this results in a hang during fc-cache generation - this took a significant effort to determine
./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --with-zlib=no --enable-shared --disable-static
make
make install
cd ..

#-----shine shared lib for ARMv5 only (no FPU)
[ "${TOOLCHAIN}" == "/usr/local/arm-none-linux-gnueabi" ] && cd toots-shine-*
[ "${TOOLCHAIN}" == "/usr/local/arm-none-linux-gnueabi" ] && ./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static
[ "${TOOLCHAIN}" == "/usr/local/arm-none-linux-gnueabi" ] && make
[ "${TOOLCHAIN}" == "/usr/local/arm-none-linux-gnueabi" ] && make install
[ "${TOOLCHAIN}" == "/usr/local/arm-none-linux-gnueabi" ] && cd ..

#-----misc shared libs for Intel Evansport FFmpeg build only
#-----use libx264 version from Synology sources since the Evansport ffmpeg may have specific dependencies on this version
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && cd x264
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && $X264_CONFIG
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && make
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && make install
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && cd ..
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && cd libxml2-2.x
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && ./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static --without-python
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && make
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && make install
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && cd ..
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && cd json-c-0.x
#-----undefined reference to `rpl_malloc' when linking ffmpeg
#-----http://rickfoosusa.blogspot.co.uk/2011/11/howto-fix-undefined-reference-to.html 
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && ac_cv_func_malloc_0_nonnull=yes ac_cv_func_realloc_0_nonnull=yes ./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu  --enable-shared --disable-static
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && make
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && make install
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && cd ..

#-----fribidi shared lib
wget http://fribidi.org/download/fribidi-0.19.6.tar.bz2
tar xvjf fribidi-0.19.6.tar.bz2
cd fribidi-0.19.6
#-----patch fribidi source since it won't compile - some sort of page size function is no longer available
#-----error on Intel: fribidi-run.c:70: error: 'PAGE_SIZE' undeclared (first use in this function)
#-----http://armbedded.eu/node/56
#-----http://lists.debian.org/debian-glibc/2006/10/msg00169.html
#-----http://my.safaribooksonline.com/book/operating-systems-and-server-administration/linux/0596009585/advanced-file-i-o/mapping_files_into_memory
[ "${CROSS_PREFIX}" == "i686-pc-linux-gnu" ] && sed -i "s|include <asm/page.h>|include <unistd.h>\n#   define PAGE_SIZE sysconf(_SC_PAGESIZE)|" lib/common.h
./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static
make
make install
cd ..

#-----fontconfig shared lib
wget http://www.freedesktop.org/software/fontconfig/release/fontconfig-2.11.1.tar.gz
tar xvzf fontconfig-2.11.1.tar.gz
cd fontconfig-2.11.1
LDFLAGS=-Wl,-L${TOOLCHAIN}/lib,-rpath,\'\$\$ORIGIN/../lib:\$\$ORIGIN\' ./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static
#-----correct some non-standard library linking behaviour
sed -i "s/^hardcode_into_libs=yes/hardcode_into_libs=no/" libtool
make
make install
cd ..

#-----libass shared lib
wget https://github.com/libass/libass/releases/download/0.12.1/libass-0.12.1.tar.xz
tar xvJf libass-0.12.1.tar.xz
cd libass-0.12.1
LDFLAGS=-Wl,-L${TOOLCHAIN}/lib,-rpath,\'\$\$ORIGIN\' ./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static
#-----correct some non-standard library linking behaviour
sed -i "s/^hardcode_into_libs=yes/hardcode_into_libs=no/" libtool
make
make install
cd ..
#-----Although libass would link correctly to older FFmpeg versions, this mod is now necessary:
#-----http://ffmpeg.org/pipermail/ffmpeg-user/2012-November/010906.html
#-----no longer needed, note here in case of regression in future versions 
#sed -i -r "s/(^Libs: .*$)/\1 -lfontconfig -lfribidi -lfreetype -lexpat/" $TOOLCHAIN/lib/pkgconfig/libass.pc

#-----libx264 shared lib
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] || wget ftp://ftp.videolan.org/pub/x264/snapshots/last_x264.tar.bz2
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] || tar xvjf last_x264.tar.bz2
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] || cd x264-snapshot-*
#-----NEON ASM needs to be built by GCC assembler not AS
[ "${TOOLCHAIN}" == "/usr/local/arm-cortexa9-linux-gnueabi" ] && export AS=${TOOLCHAIN}/bin/${CROSS_PREFIX}-gcc
#-----intel ASM needs to be built by yasm assembler
[ "${TOOLCHAIN}" == "/usr/local/i686-pc-linux-gnu" ] && export AS=yasm
[ "${TOOLCHAIN}" == "/usr/local/x86_64-pc-linux-gnu" ] && export AS=yasm
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] || $X264_CONFIG
#-----back out assembler changes
[ "${TOOLCHAIN}" == "/usr/local/arm-cortexa9-linux-gnueabi" ] && export AS=${TOOLCHAIN}/bin/${CROSS_PREFIX}-as
[ "${TOOLCHAIN}" == "/usr/local/i686-pc-linux-gnu" ] && export AS=${TOOLCHAIN}/bin/${CROSS_PREFIX}-as
[ "${TOOLCHAIN}" == "/usr/local/x86_64-pc-linux-gnu" ] && export AS=${TOOLCHAIN}/bin/${CROSS_PREFIX}-as
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] || make
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] || make install
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] || cd ..

#-----GMP shared lib
wget https://gmplib.org/download/gmp/gmp-6.0.0a.tar.xz
tar -xJf gmp-6.0.0a.tar.xz
cd gmp-6.0.0
#-----disable ARM Thumb - GMP doesn't compile in Thumb mode even using this patch
#-----https://gmplib.org/list-archives/gmp-discuss/2014-March/005537.html
export MARCH=`echo $MARCH | sed -e "s/ -mthumb//"`
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] || export CFLAGS="-I${TOOLCHAIN}/include -O3 ${MARCH}"
[ "${TOOLCHAIN}" == "/usr/local/powerpc-none-linux-gnuspe" ] && mv ${TOOLCHAIN}/bin/flex ${TOOLCHAIN}/bin/flex.old
./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static
make
make install
#-----re-enable thumb-2 mode where applicable
[ "${TOOLCHAIN}" == "/usr/local/arm-cortexa9-linux-gnueabi" ] && export MARCH="${MARCH} -mthumb" 
[ "${TOOLCHAIN}" == "/usr/local/arm-marvell-linux-gnueabi" ] && export MARCH="${MARCH} -mthumb"
[ "${TOOLCHAIN}" == "/usr/local/armv7-marvell-linux-gnueabi-hard" ] && export MARCH="${MARCH} -mthumb"
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] || export CFLAGS="-I${TOOLCHAIN}/include -O3 ${MARCH}" 
cd ..

#-----nettle shared lib
#-----version compatibility issues with gnutls: https://crux.nu/bugs/?do=details&task_id=1023
wget https://ftp.gnu.org/gnu/nettle/nettle-2.7.1.tar.gz
tar xvzf nettle-2.7.1.tar.gz
cd nettle-2.7.1
LDFLAGS=-Wl,-L${TOOLCHAIN}/lib,-rpath,\'\$\$ORIGIN\' ./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static --disable-openssl
make
make install
#-----this will fail to complete but it's not important: *** No rule to make target `../libnettle.a', needed by `sexp-conv'. Stop.
cd ..

#-----gnutls shared lib
wget ftp://ftp.gnutls.org/gcrypt/gnutls/v3.3/gnutls-3.3.14.tar.xz
tar -xJf gnutls-3.3.14.tar.xz 
cd gnutls-3.3.14
#-----use DSM's existing root CA certs bundle
LDFLAGS=-Wl,-L${TOOLCHAIN}/lib,-rpath,\'\$\$ORIGIN\' ./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --with-default-trust-store-file=/etc/ssl/certs/ca-certificates.crt --enable-threads=posix --disable-doc --enable-shared --disable-static
#-----correct some non-standard library linking behaviour
sed -i "s/^hardcode_into_libs=yes/hardcode_into_libs=no/" libtool
make
make install
sed -i -r "s/(^Libs: .*$)/\1 -lnettle -lhogweed -lgmp/" $TOOLCHAIN/lib/pkgconfig/gnutls.pc
cd ..

#-----librtmp shared lib
git clone git://git.ffmpeg.org/rtmpdump
cd rtmpdump
make CROSS_COMPILE=${TOOLCHAIN}/bin/${CROSS_PREFIX}- SYS=posix prefix=${TOOLCHAIN} CRYPTO=GNUTLS INC=-I${TOOLCHAIN}/include XLDFLAGS=-Wl,-L${TOOLCHAIN}/lib,-rpath,\'\$\$ORIGIN\'
#------librtmp.pc will have the wrong lib dependencies defined unless GNUTLS is specified again here
make install prefix=${TOOLCHAIN} CRYPTO=GNUTLS
cd ..

#-----libspeex shared lib
wget http://downloads.xiph.org/releases/speex/speex-1.2rc2.tar.gz
tar xvzf speex-1.2rc2.tar.gz
cd speex-1.2rc2
./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static
make
make install
cd ..

#-----FFmpeg (Evansport uses a separate Synology-modified source bundle)
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] || wget https://www.ffmpeg.org/releases/ffmpeg-2.7.1.tar.gz
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] || tar xvzf ffmpeg-2.7.1.tar.gz
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] || cd ffmpeg-2.7.1
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && wget https://dl.dropboxusercontent.com/u/1188556/DSM5565-ffmpeg-2.0.x-src.tar.xz
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && tar xvJf DSM5565-ffmpeg-2.0.x-src.tar.xz
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && cd ffmpeg-2.0.x

#-----Apply DTS audio remux fix for hardware players (incorrect stream ID was set in older versions of FFmpeg)
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && wget http://dl.dropboxusercontent.com/u/1188556/ffmpeg-ticket1398.patch
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && patch --verbose -p1 < ffmpeg-ticket1398.patch

#-----PowerPC-specific fix originally provided by Synology Support in Oct 2012, adapted after code re-org by ffmpeg
[ "${CROSS_PREFIX}" == "powerpc-none-linux-gnuspe" ] && wget http://dl.dropboxusercontent.com/u/1188556/ffmpeg-syno-ppc.patch
[ "${CROSS_PREFIX}" == "powerpc-none-linux-gnuspe" ] && patch --verbose -p1 < ffmpeg-syno-ppc.patch

#-----Evansport SMD driver scheduler uses inline asm which breaks compilation without -fasm
#-----http://stackoverflow.com/questions/18574943/gcc-inline-assembler-implicit-function-declaration-on-powerpc-arch
#-----http://ffmpeg.org/pipermail/ffmpeg-devel/2008-October/056533.html
#-----http://www.tldp.org/HOWTO/Assembly-HOWTO/gcc.html
#-----we also need to add the intelce-utlities locations of the SMD libraries
#-----libx264 will fail to compile with this added to include path, so only add it just before compiling ffmpeg
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] \
  && export CFLAGS="${CFLAGS} -fasm -I${TOOLCHAIN}/include/json-c -I${TOOLCHAIN}/include/intelce-utilities -I${TOOLCHAIN}/include/intelce-utilities/linux_user"

#-----Evansport SMD functionality is disabled by default
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && sed -i "1i#define SYNO_ENABLE_SMD" synoconfig.h
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && sed -i "1i#define SYNO_EVANSPORT" synoconfig.h

#-----set library linker to use relative library search path (../lib and .)
#-----tricky shell escaping of $ORIGIN is required http://itee.uq.edu.au/~daniel/using_origin/
#-----http://stackoverflow.com/questions/6562403/i-dont-understand-wl-rpath-wl
#-----however although FFmpeg gets a correct rpath, the downstream libraries get a value of "RIGIN/../lib:RIGIN"
#-----owing to some additional variable substitution happening - easier to build with XORIGIN and use chrpath later
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] \
  || LDFLAGS=-Wl,-L${TOOLCHAIN}/lib,-rpath,XORIGIN/../lib:XORIGIN,-rpath-link,${TOOLCHAIN}/lib ${FF_CONFIG} \
  && LDFLAGS=-Wl,-L${TOOLCHAIN}/lib,-L${TOOLCHAIN}/lib/intelce-utilities,-rpath,XORIGIN/../lib:XORIGIN,-rpath-link,${TOOLCHAIN}/lib,-rpath-link,${TOOLCHAIN}/lib/intelce-utilities ${FF_CONFIG}

#-----external libs (librtmp, libass) don't seem to link to ffmpeg (check ffmpeg binary with objdump -x)
#-----this is in fact normal because LDFLAGS as defined in config.mak contains '--as-needed'
#-----https://wiki.gentoo.org/wiki/Project:Quality_Assurance/As-needed
#-----e.g. FFmpeg does not contain any symbols from librtmp, but libavformat does
#-----use ldd on the target system and the full dependency chain will be shown
make
make install

#-----remove intelce-utilities from include path
[ "${TOOLCHAIN}" == "/usr/local/evansport-pc-linux-gnu" ] && export CFLAGS="-I${TOOLCHAIN}/include -O3 ${MARCH}"

mkdir native-${CROSS_PREFIX}
cd native-${CROSS_PREFIX}

#-----evansport FFmpeg 2.0.2
#-----fix up rpath settings
sudo chrpath -r '$ORIGIN/../lib:$ORIGIN' ${TOOLCHAIN}/bin/ffmpeg
sudo chrpath -r '$ORIGIN' ${TOOLCHAIN}/lib/libavcodec.so.55
sudo chrpath -r '$ORIGIN' ${TOOLCHAIN}/lib/libavdevice.so.55
sudo chrpath -r '$ORIGIN' ${TOOLCHAIN}/lib/libavfilter.so.3
sudo chrpath -r '$ORIGIN' ${TOOLCHAIN}/lib/libavformat.so.55
sudo chrpath -r '$ORIGIN' ${TOOLCHAIN}/lib/libavutil.so.52
sudo chrpath -r '$ORIGIN' ${TOOLCHAIN}/lib/libpostproc.so.52
sudo chrpath -r '$ORIGIN' ${TOOLCHAIN}/lib/libswresample.so.0
sudo chrpath -r '$ORIGIN' ${TOOLCHAIN}/lib/libswscale.so.2
cp ${TOOLCHAIN}/lib/libavcodec.so.55 .
cp ${TOOLCHAIN}/lib/libavdevice.so.55 .
cp ${TOOLCHAIN}/lib/libavfilter.so.3 .
cp ${TOOLCHAIN}/lib/libavformat.so.55 .
cp ${TOOLCHAIN}/lib/libavutil.so.52 .
cp ${TOOLCHAIN}/lib/libpostproc.so.52 .
cp ${TOOLCHAIN}/lib/libswresample.so.0 .
cp ${TOOLCHAIN}/lib/libswscale.so.2 .
cp ${TOOLCHAIN}/lib/libx264.so.119 .

#-----FFmpeg 2.7.1
#-----fix up rpath settings
sudo chrpath -r '$ORIGIN/../lib:$ORIGIN' ${TOOLCHAIN}/bin/ffmpeg
sudo chrpath -r '$ORIGIN' ${TOOLCHAIN}/lib/libavcodec.so.56
sudo chrpath -r '$ORIGIN' ${TOOLCHAIN}/lib/libavdevice.so.56
sudo chrpath -r '$ORIGIN' ${TOOLCHAIN}/lib/libavfilter.so.5
sudo chrpath -r '$ORIGIN' ${TOOLCHAIN}/lib/libavformat.so.56
sudo chrpath -r '$ORIGIN' ${TOOLCHAIN}/lib/libavutil.so.54
sudo chrpath -r '$ORIGIN' ${TOOLCHAIN}/lib/libpostproc.so.53
sudo chrpath -r '$ORIGIN' ${TOOLCHAIN}/lib/libswresample.so.1
sudo chrpath -r '$ORIGIN' ${TOOLCHAIN}/lib/libswscale.so.3
cp ${TOOLCHAIN}/lib/libavcodec.so.56 .
cp ${TOOLCHAIN}/lib/libavdevice.so.56 .
cp ${TOOLCHAIN}/lib/libavfilter.so.5 .
cp ${TOOLCHAIN}/lib/libavformat.so.56 .
cp ${TOOLCHAIN}/lib/libavutil.so.54 .
cp ${TOOLCHAIN}/lib/libpostproc.so.53 .
cp ${TOOLCHAIN}/lib/libswresample.so.1 .
cp ${TOOLCHAIN}/lib/libswscale.so.3 .
cp ${TOOLCHAIN}/lib/libx264.so.146 .

cp ${TOOLCHAIN}/bin/ffmpeg .
cp ${TOOLCHAIN}/bin/fc-cache .
cp ${TOOLCHAIN}/lib/libass.so.5 .
cp ${TOOLCHAIN}/lib/libfontconfig.so.1 .
cp ${TOOLCHAIN}/lib/libfribidi.so.0 .
cp ${TOOLCHAIN}/lib/libnettle.so.4 .
cp ${TOOLCHAIN}/lib/libhogweed.so.2 .
cp ${TOOLCHAIN}/lib/libgnutls.so.28 .
cp ${TOOLCHAIN}/lib/librtmp.so.1 .
cp ${TOOLCHAIN}/lib/libspeex.so.1 .
[ ${TOOLCHAIN} == "/usr/local/arm-none-linux-gnueabi" ] && cp ${TOOLCHAIN}/lib/libshine.so.3 .
#cp ${TOOLCHAIN}/lib/libfdk-aac.so.1 .
sudo chmod +x *
sudo chown root:root *
XZ_OPT=-9 tar -cvJf ffmpeg-${CROSS_PREFIX}.tar.xz *
 

Here’s how the library dependencies look. There are no links to any libraries in /opt, and DSM-bundled libraries in /lib are linked to where possible. All the other new libraries are kept within the Serviio lib folder (../lib relative to the ffmpeg binary).

DS111> ldd /var/packages/Serviio/target/bin/ffmpeg
	libavdevice.so.56 => /volume1/@appstore/Serviio/bin/../lib/libavdevice.so.56 (0x40031000)
	libavfilter.so.5 => /volume1/@appstore/Serviio/bin/../lib/libavfilter.so.5 (0x4004f000)
	libavformat.so.56 => /volume1/@appstore/Serviio/bin/../lib/libavformat.so.56 (0x40174000)
	libavcodec.so.56 => /volume1/@appstore/Serviio/bin/../lib/libavcodec.so.56 (0x40321000)
	libpostproc.so.53 => /volume1/@appstore/Serviio/bin/../lib/libpostproc.so.53 (0x4138d000)
	libswresample.so.1 => /volume1/@appstore/Serviio/bin/../lib/libswresample.so.1 (0x413ad000)
	libswscale.so.3 => /volume1/@appstore/Serviio/bin/../lib/libswscale.so.3 (0x413d1000)
	libavutil.so.54 => /volume1/@appstore/Serviio/bin/../lib/libavutil.so.54 (0x41449000)
	libm.so.6 => /lib/libm.so.6 (0x414c2000)
	libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x4156b000)
	libpthread.so.0 => /lib/libpthread.so.0 (0x41587000)
	libc.so.6 => /lib/libc.so.6 (0x415b0000)
	libass.so.5 => /volume1/@appstore/Serviio/bin/../lib/libass.so.5 (0x416f3000)
	librtmp.so.1 => /volume1/@appstore/Serviio/bin/../lib/librtmp.so.1 (0x41726000)
	libz.so.1 => /lib/libz.so.1 (0x4174e000)
	libgnutls.so.28 => /volume1/@appstore/Serviio/bin/../lib/libgnutls.so.28 (0x41772000)
	libx264.so.146 => /volume1/@appstore/Serviio/bin/../lib/libx264.so.146 (0x4187a000)
	libspeex.so.1 => /volume1/@appstore/Serviio/bin/../lib/libspeex.so.1 (0x419ea000)
	libshine.so.3 => /volume1/@appstore/Serviio/bin/../lib/libshine.so.3 (0x41a14000)
	librt.so.1 => /lib/librt.so.1 (0x41a2e000)
	/lib/ld-linux.so.3 (0x40000000)
	libfribidi.so.0 => /volume1/@appstore/Serviio/bin/../lib/libfribidi.so.0 (0x41a45000)
	libfontconfig.so.1 => /volume1/@appstore/Serviio/bin/../lib/libfontconfig.so.1 (0x41a6c000)
	libexpat.so.1 => /lib/libexpat.so.1 (0x41ab9000)
	libfreetype.so.6 => /lib/libfreetype.so.6 (0x41adb000)
	libhogweed.so.2 => /volume1/@appstore/Serviio/bin/../lib/libhogweed.so.2 (0x41b7e000)
	libnettle.so.4 => /volume1/@appstore/Serviio/bin/../lib/libnettle.so.4 (0x41bb5000)
	libgmp.so.10 => /lib/libgmp.so.10 (0x41bfc000)
	libbz2.so.1.0 => /lib/libbz2.so.1.0 (0x41c6b000)
	libpng16.so.16 => /lib/libpng16.so.16 (0x41c7c000)
DS111>
 

Here’s how the even more complex Evansport build with hardware transcoding support looks:

DS214Play> ldd /var/packages/Serviio/target/bin/ffmpeg
	linux-gate.so.1 =>  (0xffffe000)
	libavdevice.so.55 => /volume1/@appstore/Serviio/bin/../lib/libavdevice.so.55 (0xb775c000)
	libavfilter.so.3 => /volume1/@appstore/Serviio/bin/../lib/libavfilter.so.3 (0xb767d000)
	libavformat.so.55 => /volume1/@appstore/Serviio/bin/../lib/libavformat.so.55 (0xb74fd000)
	libavcodec.so.55 => /volume1/@appstore/Serviio/bin/../lib/libavcodec.so.55 (0xb677b000)
	libpostproc.so.52 => /volume1/@appstore/Serviio/bin/../lib/libpostproc.so.52 (0xb674b000)
	libswresample.so.0 => /volume1/@appstore/Serviio/bin/../lib/libswresample.so.0 (0xb6733000)
	libswscale.so.2 => /volume1/@appstore/Serviio/bin/../lib/libswscale.so.2 (0xb66c9000)
	libavutil.so.52 => /volume1/@appstore/Serviio/bin/../lib/libavutil.so.52 (0xb6687000)
	libffmpeg_plugin.so => /lib/libffmpeg_plugin.so (0xb6672000)
	libm.so.6 => /lib/libm.so.6 (0xb6648000)
	libjson-c.so.2 => /lib/libjson-c.so.2 (0xb663d000)
	libpthread.so.0 => /lib/libpthread.so.0 (0xb6622000)
	libc.so.6 => /lib/libc.so.6 (0xb64aa000)
	libismd_core.so => /lib/libismd_core.so (0xb649d000)
	libismd_viddec.so => /lib/libismd_viddec.so (0xb6494000)
	librt.so.1 => /lib/librt.so.1 (0xb648b000)
	libass.so.5 => /volume1/@appstore/Serviio/bin/../lib/libass.so.5 (0xb645f000)
	librtmp.so.1 => /volume1/@appstore/Serviio/bin/../lib/librtmp.so.1 (0xb6443000)
	libz.so.1 => /lib/libz.so.1 (0xb642d000)
	libgnutls.so.28 => /volume1/@appstore/Serviio/bin/../lib/libgnutls.so.28 (0xb6306000)
	libx264.so.119 => /volume1/@appstore/Serviio/bin/../lib/libx264.so.119 (0xb61b3000)
	libspeex.so.1 => /volume1/@appstore/Serviio/bin/../lib/libspeex.so.1 (0xb6193000)
	libmp3lame.so.0 => /lib/libmp3lame.so.0 (0xb6121000)
	libdl.so.2 => /lib/libdl.so.2 (0xb611c000)
	libxml2.so.2 => /lib/libxml2.so.2 (0xb5fc5000)
	libismd_demux.so => /lib/libismd_demux.so (0xb5fba000)
	libismd_vidrend.so => /lib/libismd_vidrend.so (0xb5fb2000)
	libismd_clock.so => /lib/libismd_clock.so (0xb5fb0000)
	libismd_audio.so => /lib/libismd_audio.so (0xb5fa1000)
	libismd_videnc.so => /lib/libismd_videnc.so (0xb5f9d000)
	libismd_vidpproc.so => /lib/libismd_vidpproc.so (0xb5f96000)
	libismdmessage.so => /lib/libismdmessage.so (0xb5f93000)
	libsven.so => /lib/libsven.so (0xb5e53000)
	libpal.so => /lib/libpal.so (0xb5e51000)
	libosal.so => /lib/libosal.so (0xb5e49000)
	libplatform_config.so => /lib/libplatform_config.so (0xb5e46000)
	libsystem_utils.so => /lib/libsystem_utils.so (0xb5e43000)
	libismd_clock_recovery.so => /lib/libismd_clock_recovery.so (0xb5e40000)
	/lib/ld-linux.so.2 (0xb7768000)
	libfribidi.so.0 => /volume1/@appstore/Serviio/bin/../lib/libfribidi.so.0 (0xb5e27000)
	libfontconfig.so.1 => /volume1/@appstore/Serviio/bin/../lib/libfontconfig.so.1 (0xb5de4000)
	libexpat.so.1 => /lib/libexpat.so.1 (0xb5dbc000)
	libfreetype.so.6 => /lib/libfreetype.so.6 (0xb5d08000)
	libhogweed.so.2 => /volume1/@appstore/Serviio/bin/../lib/libhogweed.so.2 (0xb5cdb000)
	libnettle.so.4 => /volume1/@appstore/Serviio/bin/../lib/libnettle.so.4 (0xb5ca3000)
	libgmp.so.10 => /lib/libgmp.so.10 (0xb5c33000)
	liblzma.so.5 => /lib/liblzma.so.5 (0xb5c0d000)
	libbz2.so.1.0 => /lib/libbz2.so.1.0 (0xb5bfc000)
	libpng16.so.16 => /lib/libpng16.so.16 (0xb5bc4000)
DS214Play> 

Bliss album art manager package for Synology NAS

bliss-UI

 

Bliss is a Java application written by Dan Gravell which can manage file names, tags and album art for your music collection and which can also enforce their consistency. It is designed to be left running once installed so that albums you add later will have these same policies applied to them automatically. It supports a wide range of music formats, and effortlessly deals with very large collections – apparently even ones containing quite obscure recordings. My own collection didn’t really put this to the test, since it doesn’t contain bootlegs, live sets or rarities plus I had also already obtained cover art for it (from back when CoverFlow first graced the screen of my iPhone 2G).

I could see from referrals to this blog that people were asking Dan for a Synology package which he didn’t have the time to investigate, and so I thought that it would make an interesting little project, especially since a NAS is the ideal device to run Bliss on. Although there was already a Howto post on the Synology forums, that guide only really covered getting the basic functionality of Bliss up and running – it was missing the best bits: the filesystem watching and audio fingerprinting features. These depend on natively compiled binaries, which Bliss doesn’t include for ARM or PowerPC CPUs. Getting these working provided precisely the sort of challenge I like. Not only were they difficult to compile, but getting them integrated into the various OSGi ‘bundles’ that make up Bliss was quite involved too.

Bliss uses an open source library called Chromaprint, itself part of the wider Acoustid project. The aim is to scan an audio file, to produce a fingerprint of the sound, and then to compare this against an open online database such as MusicBrainz.org to identify music regardless of compression codec used. Its author Lukáš Lalinský explains how it works.

 

Synology Package Installation

  • In Synology DSM’s Package Center, click Settings and add my package repository:
    Add Package Repository
  • The repository will push its certificate automatically to the NAS, which is used to validate package integrity. Set the Trust Level to Synology Inc. and trusted publishers:
    Trust Level
  • Since Bliss is a Java application, you will need to install one of my Java SE Embedded packages first (Java 7 or 8) if you have not already done so. Read the instructions on that page carefully too.
  • Now browse the Community section in Package Center to install Bliss:
    Community-packages
    The repository only displays packages which are compatible with your specific model of NAS. If you don’t see Bliss in the list, then either your NAS model or your DSM version are not supported at this time. DSM 5.0 is the minimum supported version for this package, though you will need DSM 6.0 or later for audio fingerprinting support.
  • When the Bliss package is running you can manage it using the icon in the main DSM application menu using the button in the top left corner:
    bliss-webui
 

Package scripts

For information, here are the package scripts so you can see what it’s going to do. You can get more information about how packages work by reading the Synology 3rd Party Developer Guide.

installer.sh

#!/bin/sh

#--------BLISS installer script
#--------package maintained at pcloadletter.co.uk


DOWNLOAD_URL="`wget -qO- http://www.blisshq.com/app/latest-linux-version`"
DOWNLOAD_FILE="`echo ${DOWNLOAD_URL} | sed -r "s%^.*/(.*)%\1%"`"
SYNO_CPU_ARCH="`uname -m`"
[ "${SYNO_CPU_ARCH}" == "x86_64" ] && [ ${SYNOPKG_DSM_VERSION_MAJOR} -ge 6 ] && SYNO_CPU_ARCH="x64"
[ "${SYNO_CPU_ARCH}" == "x86_64" ] && SYNO_CPU_ARCH="i686"
[ "${SYNOPKG_DSM_ARCH}" == "armada375" ] && SYNO_CPU_ARCH="armv7l"
[ "${SYNOPKG_DSM_ARCH}" == "armada38x" ] && SYNO_CPU_ARCH="armhfneon"
[ "${SYNOPKG_DSM_ARCH}" == "comcerto2k" ] && SYNO_CPU_ARCH="armhfneon"
[ "${SYNOPKG_DSM_ARCH}" == "alpine" ] && SYNO_CPU_ARCH="armhfneon"
[ "${SYNOPKG_DSM_ARCH}" == "alpine4k" ] && SYNO_CPU_ARCH="armhfneon"
[ "${SYNOPKG_DSM_ARCH}" == "monaco" ] && SYNO_CPU_ARCH="armhfneon"
[ ${SYNOPKG_DSM_VERSION_MAJOR} -ge 6 ] && NATIVE_BINS_URL="http://packages.pcloadletter.co.uk/downloads/bliss-native-${SYNO_CPU_ARCH}.tar.xz"
NATIVE_BINS_FILE="`echo ${NATIVE_BINS_URL} | sed -r "s%^.*/(.*)%\1%"`"
#'ua' prefix means wget user-agent will be customized
INSTALL_FILES="ua${DOWNLOAD_URL} ${NATIVE_BINS_URL}"
PID_FILE="${SYNOPKG_PKGDEST}/bliss.pid"
TEMP_FOLDER="`find / -maxdepth 2 -path '/volume?/@tmp' | head -n 1`"
APP_TEMP="${TEMP_FOLDER}/${SYNOPKG_PKGNAME}"
source /etc/profile


pre_checks ()
{
  if [ -z ${JAVA_HOME} ]; then
    echo "Java is not installed or not properly configured. JAVA_HOME is not defined. " >> $SYNOPKG_TEMP_LOGFILE
    echo "Download and install the Java Synology package from http://wp.me/pVshC-z5" >> $SYNOPKG_TEMP_LOGFILE
    exit 1
  fi

  if [ ! -f ${JAVA_HOME}/bin/java ]; then
    echo "Java is not installed or not properly configured. The Java binary could not be located. " >> $SYNOPKG_TEMP_LOGFILE
    echo "Download and install the Java Synology package from http://wp.me/pVshC-z5" >> $SYNOPKG_TEMP_LOGFILE
    exit 1
  fi

  JAVA_VER=`java -version 2>&1 | sed -r "/^.* version/!d;s/^.* version \"[0-9]\.([0-9]).*$/\1/"`
  if [ ${JAVA_VER} -lt 7 ]; then
    echo "This version of Bliss requires Java 7 or newer. Please update your Java package. " >> $SYNOPKG_TEMP_LOGFILE
    exit 1
  fi

  if [ ${SYNOPKG_DSM_VERSION_MAJOR} -lt 6 ]; then
    echo "Please note that native binary support for song identification via audio fingerprinting requires DSM 6.0" >> $SYNOPKG_TEMP_LOGFILE
  fi
}


preinst ()
{
  pre_checks
  cd ${TEMP_FOLDER}
  for WGET_URL in ${INSTALL_FILES}
  do
    WGET_FILENAME="`echo ${WGET_URL} | sed -r "s%^.*/(.*)%\1%"`"
    [ -f ${TEMP_FOLDER}/${WGET_FILENAME} ] && rm ${TEMP_FOLDER}/${WGET_FILENAME}
    #this will allow blisshq.com to track the number of downloads from Synology users
    WGET_URL=`echo ${WGET_URL} | sed -e "s/^ua/--user-agent=Synology --referer=http:\/\/pcloadletter.co.uk\/2012\/09\/17\/bliss-package-for-synology /"`
    wget ${WGET_URL}
    if [[ $? != 0 ]]; then
      if [ -d ${PUBLIC_FOLDER} ] && [ -f ${PUBLIC_FOLDER}/${WGET_FILENAME} ]; then
        cp ${PUBLIC_FOLDER}/${WGET_FILENAME} ${TEMP_FOLDER}
      else     
        echo "There was a problem downloading ${WGET_FILENAME} from the official download link, " >> $SYNOPKG_TEMP_LOGFILE
        echo "which was \"${WGET_URL}\" " >> $SYNOPKG_TEMP_LOGFILE
        echo "Alternatively, you may download this file manually and place it in the 'public' shared folder. " >> $SYNOPKG_TEMP_LOGFILE
        exit 1
      fi
    fi
  done
 
  exit 0
}


postinst ()
{
  #run the installer
  cd ${TEMP_FOLDER}
  echo "INSTALL_PATH=${SYNOPKG_PKGDEST}" > ${TEMP_FOLDER}/bliss-synology.properties
  java -jar ${TEMP_FOLDER}/${DOWNLOAD_FILE} -options ${TEMP_FOLDER}/bliss-synology.properties > /dev/null && rm ${TEMP_FOLDER}/${DOWNLOAD_FILE}
  rm ${TEMP_FOLDER}/bliss-synology.properties
  sed -i "s%^#!/bin/bash%#!/bin/sh%" ${SYNOPKG_PKGDEST}/bin/bliss.sh
  
  #stow jar files containing Synology versions of native code
  if [ -f ${TEMP_FOLDER}/${NATIVE_BINS_FILE} ]; then
    mkdir ${SYNOPKG_PKGDEST}/syno-native
    cd ${SYNOPKG_PKGDEST}/syno-native
    tar xJf ${TEMP_FOLDER}/${NATIVE_BINS_FILE}
  fi
  #record the CPU architecture
  echo "${SYNO_CPU_ARCH}" > syno_cpu_arch.txt

  #make changes to Bliss launcher script so that pid file is created for Java process
  sed -r -i "s%^(exec .*)$%\1 > ${SYNOPKG_PKGDEST}/bliss.out 2>\&1 \&%" ${SYNOPKG_PKGDEST}/bin/bliss.sh
  echo "echo \$! > ${PID_FILE}" >> ${SYNOPKG_PKGDEST}/bin/bliss.sh

  #set some additional system properties (temp folder, prefs sync interval)
  EXTRA_OPTS="-Djava.io.tmpdir=${TEMP_FOLDER} -Djava.util.prefs.syncInterval=86400 -Djava.net.preferIPv4Stack=true"
  sed -r -i "s%-splash:bliss-splash.png%${EXTRA_OPTS}%" ${SYNOPKG_PKGDEST}/bin/bliss.sh
  sed -r -i "s%-XX:HeapDumpPath=/tmp%-XX:HeapDumpPath=${TEMP_FOLDER}%" ${SYNOPKG_PKGDEST}/bin/bliss.sh

  #create log file to allow package start errors to be captured
  [ -e ${SYNOPKG_PKGDEST}/bliss.out ] || touch ${SYNOPKG_PKGDEST}/bliss.out

  #add firewall config
  /usr/syno/bin/servicetool --install-configure-file --package /var/packages/${SYNOPKG_PKGNAME}/scripts/${SYNOPKG_PKGNAME}.sc > /dev/null

  exit 0
}


preuninst ()
{
  `dirname $0`/stop-start-status stop

  exit 0
}


postuninst ()
{
  #clean up temp
  [ -d ${TEMP_FOLDER}/Bliss ] && rm -rf ${TEMP_FOLDER}/Bliss

  #remove firewall config
  if [ "${SYNOPKG_PKG_STATUS}" == "UNINSTALL" ]; then
    /usr/syno/bin/servicetool --remove-configure-file --package ${SYNOPKG_PKGNAME}.sc > /dev/null
  fi

  exit 0
}


preupgrade ()
{
  `dirname $0`/stop-start-status stop
  pre_checks
  
  exit 0
}


postupgrade ()
{
  exit 0
}
 

start-stop-status.sh

#!/bin/sh

#--------BLISS start-stop-status script
#--------package maintained at pcloadletter.co.uk


PKG_FOLDER="`dirname $0 | cut -f1-4 -d'/'`"
ENGINE_SCRIPT="${PKG_FOLDER}/target/bin/bliss.sh"
DNAME="`dirname $0 | cut -f4 -d'/'`"
PID_FILE="${PKG_FOLDER}/target/bliss.pid"
DLOG="${PKG_FOLDER}/target/bliss.out"
SYNO_CPU_ARCH="`cat ${PKG_FOLDER}/target/syno-native/syno_cpu_arch.txt`"
source /etc/profile
source /root/.profile


start_daemon ()
{
  #update the package version number in case of an in-app update
  BLISS_BUNDLE_DIR="`grep -r --include='*.info' com.elsten.bliss.bundle ${PKG_FOLDER}/target/felix-cache/ | cut -f1 -d':'`"
  BLISS_BUNDLE_DIR="`dirname ${BLISS_BUNDLE_DIR}`"
  BLISS_VERSION=`grep "version" ${PKG_FOLDER}/INFO | sed -r "s/^.*([0-9]{8}).*$/\1/"`
  if [ -d ${BLISS_BUNDLE_DIR} ]; then
    find ${BLISS_BUNDLE_DIR} -name *.jar > /tmp/bliss-v-check.txt
    while IFS="" read -r FILE_TO_PARSE; do
      if [ -e ${FILE_TO_PARSE} ]; then
        #no unzip command in DSM 6.0
        if [ -e /usr/bin/7z ]; then
          DETECTED_VERSION=`7z e -so ${FILE_TO_PARSE} META-INF/MANIFEST.MF 2> /dev/null | grep Bundle-Version | cut -f4 -d'.' |  cut -c1-8`
        else
          DETECTED_VERSION=`unzip -p ${FILE_TO_PARSE} META-INF/MANIFEST.MF | grep Bundle-Version | cut -f4 -d'.' |  cut -c1-8`
        fi
      fi
      if [ ${DETECTED_VERSION} -gt ${BLISS_VERSION} ]; then
        BLISS_VERSION=${DETECTED_VERSION}
      fi
    done < /tmp/bliss-v-check.txt
  fi
  rm /tmp/bliss-v-check.txt
  sed -r -i "s/^version=\"[0-9]{8}/version=\"${BLISS_VERSION}/" /var/packages/Bliss/INFO
  
  #update the CPU-specific repository customizations (in case of an in-app update)
  #catch both armv5te and armv7l
  if [ "${SYNO_CPU_ARCH}" != "${SYNO_CPU_ARCH/arm/}" ]; then
    sed -i "s/policy\.tag\.auto\.linux\.x86/policy\.tag\.auto\.linux\.ARM_le/g" ${PKG_FOLDER}/target/bliss-bundle/repository.xml
  fi
  if [ "${SYNO_CPU_ARCH}" == "ppc" ]; then
    sed -i "s/policy\.tag\.auto\.linux\.x86/policy\.tag\.auto\.linux\.PowerPC/g" ${PKG_FOLDER}/target/bliss-bundle/repository.xml
  fi
 
  #overwrite native lib bundles with syno versions (in case of an in-app update)
  if [ -e ${PKG_FOLDER}/target/syno-native/ ]; then
    cp ${PKG_FOLDER}/target/syno-native/* ${PKG_FOLDER}/target/bliss-bundle
  fi

  cd ${PKG_FOLDER}
  ${ENGINE_SCRIPT} > /dev/null 2>> ${DLOG}
  if [ -z ${SYNOPKG_PKGDEST} ]; then
    #script was manually invoked, need this to show status change in Package Center
    [ -e ${PKG_FOLDER}/enabled ] || touch ${PKG_FOLDER}/enabled
  fi
}

    
stop_daemon ()
{
  echo "Stopping ${DNAME}" >> ${DLOG}
  kill `cat ${PID_FILE}`
  wait_for_status 1 20 || kill -9 `cat ${PID_FILE}`
  rm -f ${PID_FILE}
  if [ -z ${SYNOPKG_PKGDEST} ]; then
    #script was manually invoked, need this to show status change in Package Center
    [ -e ${PKG_FOLDER}/enabled ] && rm ${PKG_FOLDER}/enabled
  fi
}


daemon_status ()
{
  if [ -f ${PID_FILE} ] && kill -0 `cat ${PID_FILE}` > /dev/null 2>&1; then
    return
  fi
  rm -f ${PID_FILE}
  return 1
}


wait_for_status ()
{
  counter=$2
  while [ ${counter} -gt 0 ]; do
    daemon_status
    [ $? -eq $1 ] && return
    let counter=counter-1
    sleep 1
  done
  return 1
}


case $1 in
  start)
    if daemon_status; then
      echo ${DNAME} is already running with PID `cat ${PID_FILE}`
      exit 0
    else
      echo Starting ${DNAME} ...
      start_daemon
      exit $?
    fi
  ;;

  stop)
    if daemon_status; then
      echo Stopping ${DNAME} ...
      stop_daemon
      exit $?
    else
      echo ${DNAME} is not running
      exit 0
    fi
  ;;

  restart)
    stop_daemon
    start_daemon
    exit $?
  ;;

  status)
    if daemon_status; then
      echo ${DNAME} is running with PID `cat ${PID_FILE}`
      exit 0
    else
      echo ${DNAME} is not running
      exit 1
    fi
  ;;

  log)
    echo "${DLOG}"
    exit 0
  ;;

  *)
    echo "Usage: $0 {start|stop|status|restart}" >&2
    exit 1
  ;;

esac
 

Changelog:

  • 20160606-0011 Substantial overhaul for DSM 6.0, incorporating many enhancements developed for other packages, updated to Bliss version 20160606, DSM 6.0 newer is now required for audio track fingerprinting (fpcalc is compiled to depend on ffmpeg 2.7.1), added support for several newer Synology products, improved accuracy of temp folder detection, in-app updating should also be fixed
  • 20150522-0010 Substantial re-write (hence the long delay):
    Updated to Bliss version 20150522
    DSM 5.0 newer is now required (fpcalc is compiled to depend on FFmpeg 2.0.2)
    Now that Intel systems running DSM 5.0+ use a newer glibc, replacement Intel binaries are no longer needed
    Added support for Mindspeed Comcerto 2000 CPU in DS414j
    Added support for Intel Atom C2538 (avoton) CPU in various models
    Added support for ppc853x CPU in older PowerPC models
    Added support for Marvell Armada 375 CPU in DS215j
    Added support for Intel Evansport CPU in DS214Play and DS415Play
    Switched to using root account – no more adding account permissions, package upgrades will no longer break this
    DSM Firewall application definition added
    Tested with DSM Task Scheduler to allow package to start/stop at certain times of day, saving RAM when not needed
    Daemon init script now uses a proper PID file instead of the unreliable method of using grep on the output of ps
    Daemon init script can be run from the command line
    Switched to .tar.xz compression for native binaries to reduce web hosting storage footprint
    Improved accuracy of temp folder detection
    Package is now signed with repository private key
    User Agent customization while downloading Bliss package from blisshq.com to allow download stats gathering
  • 20130213-0009 Updated to Bliss 20130213, and will correctly report version in Package Center after an in-app update
  • 20130131-0008 Updated to Bliss 20130131
  • 20121112-0007 Fixes for DSM 4.2
  • 20121112-006 Updated to Bliss 20121112
  • 20121019-005 Updated to Bliss 20121019
  • 20121002-004 Updated to Bliss 20121002
  • 20120830-003 Added support for Freescale QorIQ PowerPC CPUs used in some Synology x13 series products, PowerPC processors in previous Synology generations with older glibc versions are not supported
  • 20120830-002 Hopefully fixed Java prefs polling issue that prevented NAS hibernation
  • 20120830-001 initial public release

 

Build Notes

Chromaprint uses some complex maths functions that FFmpeg can provide (specifically Fourier Transform), and FFmpeg’s shared libraries are already included with Synology DSM. Building Chromaprint linked to those existing libraries results in a minuscule 78KB build of fpcalc, rather than the statically compiled ones for various OS and CPU architectures included with Bliss, which weigh in at several megabytes each. I think I’m finally ‘getting’ what open source is all about, which is nice since that was my objective in experimenting with my NAS. To prevent fpcalc building and linking to its dynamic library libchromaprint.so and to get it to detect FFmpeg properly I had to carefully inspect the Makefiles to find the correct build syntax:
FFMPEG_DIR=${TOOLCHAIN} cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=ON -DBUILD_SHARED_LIBS=NO .
FFMPEG_DIR is the base folder from which CMake will look for lib/libavcodec.so and include/libavcodec/avfft.h.

For watching the filesystem Bliss uses JNotify to hook into the Linux kernel’s inotify subsystem. Getting this compiled was tricky. It seems no one has reported successfully compiling it for an ARM CPU, and JNotify’s author Omry Yadan wasn’t aware of anyone doing this either. The problem is that compilation halts with these errors:

In file included from ../net_contentobjects_jnotify_linux_JNotify_linux.c:43:
../inotify-syscalls.h:35:1: error: "__NR_inotify_init" redefined
In file included from /opt/include/sys/syscall.h:25,
                 from ../inotify-syscalls.h:4,
                 from ../net_contentobjects_jnotify_linux_JNotify_linux.c:43:
/opt/include/asm/unistd.h:344:1: error: this is the location of the previous definition
In file included from ../net_contentobjects_jnotify_linux_JNotify_linux.c:43:
../inotify-syscalls.h:36:1: error: "__NR_inotify_add_watch" redefined
In file included from /opt/include/sys/syscall.h:25,
                 from ../inotify-syscalls.h:4,
                 from ../net_contentobjects_jnotify_linux_JNotify_linux.c:43:
/opt/include/asm/unistd.h:345:1: error: this is the location of the previous definition
In file included from ../net_contentobjects_jnotify_linux_JNotify_linux.c:43:
../inotify-syscalls.h:37:1: error: "__NR_inotify_rm_watch" redefined
In file included from /opt/include/sys/syscall.h:25,
                 from ../inotify-syscalls.h:4,
                 from ../net_contentobjects_jnotify_linux_JNotify_linux.c:43:
/opt/include/asm/unistd.h:346:1: error: this is the location of the previous definition

By searching in a very generic way for a solution I found this post on stackoverflow.com which helped me to patch the header inotify-syscalls.h to get around the problem. Since there was no JDK for ARM embedded systems at build time I included the headers from OpenJDK6 which I took from a Ubuntu VM.

Compiling for Intel also required a fix. I was getting the same error as featured in this post on the JNotify user forum on SourceForge.net:
expected specifier-qualifier-list before ‘pid_t’

Despite some people’s apparent success simply rearranging the order of the includes in net_contentobjects_jnotify_linux_JNotify_linux.c this didn’t help me. I’m not sure quite how I stumbled upon it, but I found the solution staring at me on this page:
The size_t, ssize_t, uid_t, gid_t, off_t and pid_t types are defined as described in sys/types.h

I inserted an additional include in for sys/types.h and it compiled ok. It’s worth pointing out that, although all Intel Synology units are x86-64 architecture, the Oracle JRE for Embedded is x86 (32 bit), so I used the i686 toolchain. Synology DSM’s FFmpeg shared libraries are also 32 bit so my build of fpcalc needed to comply with this. Bliss nonetheless expects the binary to be called fpcalc_linux64 since Bliss is detecting the underlying Linux CPU architecture.

Once I had got past the obstacle of compiling the native code, I needed to liaise back and forth with Dan to understand how Bliss was dealing with its libraries and how I could replace the built-in versions. Originally this was quite a kludge but Dan has since abstracted out the native binaries into their own OSGI bundle fragments which makes things a lot easier, and allows Bliss to survive in-app updates until those native components are superseded. The Synology package provides the following architecture-specific jar files (with corresponding edits to their manifest) which contain the fpcalc binary from Chromaprint. Thank you to Dan for all the quick answers!

  • net.contentobjects.jnotify.linux.ARM_le-0.94.0.jar
  • net.contentobjects.jnotify.linux.PowerPC-0.94.0.jar
  • com.elsten.bliss.policy.tag.auto.linux.x86-1.0.1.jar
  • com.elsten.bliss.policy.tag.auto.linux.amd64-1.0.1.jar
  • com.elsten.bliss.policy.tag.auto.linux.ARM_le-1.0.1.jar (various versions)
  • com.elsten.bliss.policy.tag.auto.linux.PowerPC-1.0.1.jar
Here are my fixes to JNotify to get the native library part compiled, since they may help someone else:

 

Patch for native compile of JNotify 0.94 on ARM Synology

diff -crB jnotify-vanilla/inotify-syscalls.h jnotify-arm/inotify-syscalls.h
*** jnotify-vanilla/inotify-syscalls.h	2005-11-30 15:07:56.000000000 +0000
--- jnotify-arm/inotify-syscalls.h	2012-09-14 02:43:44.032130098 +0100
***************
*** 32,40 ****
  # define __NR_inotify_add_watch	152
  # define __NR_inotify_rm_watch	156
  #elif defined (__arm__)
! # define __NR_inotify_init	316
! # define __NR_inotify_add_watch	317
! # define __NR_inotify_rm_watch	318
  #elif defined (__SH4__)
  # define __NR_inotify_init	290
  # define __NR_inotify_add_watch	291
--- 32,46 ----
  # define __NR_inotify_add_watch	152
  # define __NR_inotify_rm_watch	156
  #elif defined (__arm__)
! # ifndef __NR_inotify_init
! #  define __NR_inotify_init     316
! # endif
! # ifndef __NR_inotify_add_watch
! #  define __NR_inotify_add_watch 317
! # endif
! # ifndef __NR_inotify_rm_watch
! #  define __NR_inotify_rm_watch 318
! # endif
  #elif defined (__SH4__)
  # define __NR_inotify_init	290
  # define __NR_inotify_add_watch	291
diff -crB jnotify-vanilla/Release/subdir.mk jnotify-arm/Release/subdir.mk
*** jnotify-vanilla/Release/subdir.mk	2011-02-28 18:07:20.000000000 +0000
--- jnotify-arm/Release/subdir.mk	2012-09-14 02:29:00.000000000 +0100
***************
*** 17,23 ****
  %.o: ../%.c
  	@echo 'Building file: $<'
  	@echo 'Invoking: GCC C Compiler'
! 	gcc -I/usr/lib/jvm/java-6-sun/include -I/usr/lib/jvm/java-6-sun/include/linux -O3 -Wall -Werror -c -fmessage-length=0 -fPIC -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o"$@" "$<"
  	@echo 'Finished building: $<'
  	@echo ' '
  
--- 17,23 ----
  %.o: ../%.c
  	@echo 'Building file: $<'
  	@echo 'Invoking: GCC C Compiler'
! 	gcc -I/volume1/public/temp/jdk_include/ -I/volume1/public/temp/jdk_include/linux -O3 -Wall -Werror -c -fmessage-length=0 -fPIC -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o"$@" "$<"
  	@echo 'Finished building: $<'
  	@echo ' '
 

Patch for cross compile of JNotify 0.94 for other Synology CPU architectures, using Ubuntu Desktop 12

diff -crB jnotify-vanilla/net_contentobjects_jnotify_linux_JNotify_linux.c jnotify-i686/net_contentobjects_jnotify_linux_JNotify_linux.c
*** jnotify-vanilla/net_contentobjects_jnotify_linux_JNotify_linux.c	2011-02-28 18:07:20.000000000 +0000
--- jnotify-xcomp/net_contentobjects_jnotify_linux_JNotify_linux.c	2012-09-14 00:41:53.455010206 +0100
***************
*** 36,41 ****
--- 36,42 ----
  #include <sys/time.h>
  #include <sys/select.h>
  #include <sys/ioctl.h>
+ #include <sys/types.h>
  #include <errno.h>
  #include <stdio.h>
  #include <unistd.h>
diff -crB jnotify-vanilla/Release/makefile jnotify-i686/Release/makefile
*** jnotify-vanilla/Release/makefile	2011-02-28 18:07:20.000000000 +0000
--- jnotify-xcomp/Release/makefile	2012-09-14 00:37:56.475007855 +0100
***************
*** 28,34 ****
  libjnotify.so: $(OBJS) $(USER_OBJS)
  	@echo 'Building target: $@'
  	@echo 'Invoking: GCC C Linker'
! 	gcc -shared -o"libjnotify.so" $(OBJS) $(USER_OBJS) $(LIBS)
  	@echo 'Finished building target: $@'
  	@echo ' '
  
--- 28,34 ----
  libjnotify.so: $(OBJS) $(USER_OBJS)
  	@echo 'Building target: $@'
  	@echo 'Invoking: GCC C Linker'
! 	$(CC) -shared -o"libjnotify.so" $(OBJS) $(USER_OBJS) $(LIBS)
  	@echo 'Finished building target: $@'
  	@echo ' '
  
diff -crB jnotify-vanilla/Release/subdir.mk jnotify-i686/Release/subdir.mk
*** jnotify-vanilla/Release/subdir.mk	2011-02-28 18:07:20.000000000 +0000
--- jnotify-xcomp/Release/subdir.mk	2012-09-14 00:37:33.835007731 +0100
***************
*** 17,23 ****
  %.o: ../%.c
  	@echo 'Building file: $<'
  	@echo 'Invoking: GCC C Compiler'
! 	gcc -I/usr/lib/jvm/java-6-sun/include -I/usr/lib/jvm/java-6-sun/include/linux -O3 -Wall -Werror -c -fmessage-length=0 -fPIC -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o"$@" "$<"
  	@echo 'Finished building: $<'
  	@echo ' '
  
--- 17,23 ----
  %.o: ../%.c
  	@echo 'Building file: $<'
  	@echo 'Invoking: GCC C Compiler'
! 	$(CC) -I$(TOOLCHAIN)/include -O3 -Wall -Werror -c -fmessage-length=0 -fPIC -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o"$@" "$<"
  	@echo 'Finished building: $<'
  	@echo ' '
 
 

Unified Windows PE 4.0 builder for Windows ADK

This script will build Windows PE 4.0 (for x86, or AMD64 or both) including scripts and drivers of your choosing, it will create ISO images with both BIOS and UEFI support, and will also upload the resulting WIM boot images to your WDS server automatically (and freshen them if they have been re-created). This reduces the tiresome task of boot image maintenance to just a couple of clicks.

It uses only the standard Microsoft Windows ADK tools, which is the new name for WAIK. Just save the code below as Build_WinPE.cmd and right-click on it to Run as Administrator. Notice the defined variables at the start, particularly the %SOURCE% folder. It supports using either the 32bit or the 64bit ADK, and only the Windows PE and Deployment Tools ADK components are required. The script expects the following folders:

  • %SOURCE%\scripts\WinPE – any additional scripts (e.g. OS build scripts)
  • %SOURCE%\drivers\WinPE-x86\CURRENT – drivers
  • %SOURCE%\drivers\WinPE-AMD64\CURRENT
  • %SOURCE%\tools\WinPE-x86 – optional tools such as GImageX, or apps from portableapps.com
  • %SOURCE%\tools\WinPE-AMD64

Notice the optional components section at lines 90-95. Modify this if you need your image to contain additional items, for instance PowerShell or .NET Framework 4.

One further observation is that Macs don’t seem to be able to boot this version of Windows PE. I’m not sure whether this is a GOP display driver issue, or whether only true UEFI firmwares are required (Macs are EFI which is an earlier specification). To carry out an unattended Windows 8 install on a Mac via BootCamp you will need to build a Windows PE 3.0 ISO since Macs can’t PXE boot.

There’s some more info about UEFI booting on 32bit architectures here – apparently UEFI 2.3.1 compliance is a requirement. My VAIO’s Insyde H2O UEFI firmware certainly seems to ignore EFI loaders.

:: Build_WinPE.cmd
::
:: patters 2012
::
:: This script will build x86 and AMD64 Windows PE 4.0, automatically
:: collecting drivers from the relevant folders within the
:: unattended installation, building WIM and ISO images, and
:: will also upload the WIM images to the deployment server(s).
::
:: DO NOT cancel this script in progress as you can end up with
:: orphaned locks on files inside mounted WIM images which
:: usually require a reboot of the server to clear.
::

@echo off
setlocal ENABLEDELAYEDEXPANSION

::variables
     set SOURCE=\\WDSSERVER\unattended
     set PE_TEMP=C:\temp
     ::WinPE feature pack locale
     set PL=en-US
     ::commma separated list for WDS_SERVERS
     set WDS_SERVERS=WDSSERVER1,WDSSERVER2
::end variables

if "%PROCESSOR_ARCHITECTURE%"=="x86" set PRGFILES32=%PROGRAMFILES%
if "%PROCESSOR_ARCHITECTURE%"=="AMD64" set PRGFILES32=%PROGRAMFILES(X86)%

if not exist "%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools\*.*" (
     echo This script requires the Windows Assessment and Deployment Kit to be installed
     echo Download it from http://www.microsoft.com/en-us/download/details.aspx?id=30652
     echo.
     pause
     goto :eof
)
if "%1"=="relaunch" (
     call :BUILD_WINPE %2 %3 %4
     goto :eof
)
if "%1"=="unmount" (
     :: use this if you have a problem with the script and there are WIMs still mounted
     dism /Unmount-Wim /MountDir:"%PE_TEMP%\WinPE-x86\mount" /discard
     dism /Unmount-Wim /MountDir:"%PE_TEMP%\WinPE-AMD64\mount" /discard
     goto :eof
)
:prompt
cls
set /P SELECTION=Build WinPE for which CPU architecture (AMD64, x86, both)? [AMD64]: 
if "%SELECTION%"=="" set SELECTION=AMD64
if "%SELECTION%"=="amd64" set SELECTION=AMD64
if "%SELECTION%"=="X86" set SELECTION=x86
if "%SELECTION%"=="b" set SELECTION=both
if "%SELECTION%"=="BOTH" set SELECTION=both
if "%SELECTION%"=="AMD64" (
     start "Building Windows PE for AMD64 - NEVER CANCEL THIS SCRIPT IN PROGRESS" cmd /c "%0" relaunch AMD64
     goto :eof
)
if "%SELECTION%"=="x86" (
     start "Building Windows PE for x86 - NEVER CANCEL THIS SCRIPT IN PROGRESS" cmd /c "%0" relaunch x86
     goto :eof
)
if "%SELECTION%"=="both" (
     ::opening both instances of this script simultaneously seems to cause race conditions with dism.exe
     start /wait "Building Windows PE for x86 - NEVER CANCEL THIS SCRIPT IN PROGRESS" cmd /c "%0" relaunch x86 nopause
     start "Building Windows PE for AMD64 - NEVER CANCEL THIS SCRIPT IN PROGRESS" cmd /c "%0" relaunch AMD64
     goto :eof
)
goto :prompt

:BUILD_WINPE
set PE_ARCH=%1
set OSCDImgRoot=%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools\%PROCESSOR_ARCHITECTURE%\Oscdimg
set WinPERoot=%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Windows Preinstallation Environment
set DandIRoot=%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools
set DISMRoot=%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools\%PROCESSOR_ARCHITECTURE%\DISM
set PATH=%PATH%;%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools\%PROCESSOR_ARCHITECTURE%\Oscdimg
set PATH=%PATH%;%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools\%PROCESSOR_ARCHITECTURE%\BCDBoot
set PATH=%PATH%;%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools\%PROCESSOR_ARCHITECTURE%\DISM
set PATH=%PATH%;%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Windows Preinstallation Environment
echo on
rd /s /q %PE_TEMP%\WinPE-%PE_ARCH%
call copype.cmd %PE_ARCH% %PE_TEMP%\WinPE-%PE_ARCH%
::package path
set PP=%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Windows Preinstallation Environment\%PE_ARCH%\WinPE_OCs
::image path
set IP=%PE_TEMP%\WinPE-%PE_ARCH%\mount
echo on
dism /Mount-Wim /WimFile:"%PE_TEMP%\WinPE-%PE_ARCH%\media\sources\boot.wim" /Index:1 /MountDir:"%IP%"
dism /image:"%IP%" /Add-Package /PackagePath:"%PP%\WinPE-Scripting.cab"^
 /PackagePath:"%PP%\%PL%\WinPE-Scripting_%PL%.cab" /PackagePath:"%PP%\WinPE-WMI.cab"^
 /PackagePath:"%PP%\%PL%\WinPE-WMI_%PL%.cab" /PackagePath:"%PP%\WinPE-MDAC.cab"^
 /PackagePath:"%PP%\%PL%\WinPE-MDAC_%PL%.cab" /PackagePath:"%PP%\WinPE-HTA.cab"^
 /PackagePath:"%PP%\%PL%\WinPE-HTA_%PL%.cab" /PackagePath:"%PP%\WinPE-Dot3Svc.cab"^
 /PackagePath:"%PP%\%PL%\WinPE-Dot3Svc_%PL%.cab"
dism /image:"%IP%" /Add-Driver /driver:"%SOURCE%\drivers\WinPE-%PE_ARCH%\CURRENT" /Recurse
copy "%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools\%PE_ARCH%\BCDBoot\bootsect.exe" "%IP%\Windows"
copy /y "%SOURCE%\scripts\WinPE\*.*" "%IP%\Windows\System32"
copy "%SOURCE%\tools\WinPE-%PE_ARCH%\*.*" "%IP%\Windows\System32"
copy /y "%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools\%PE_ARCH%\DISM\imagex.exe" "%IP%\Windows\System32"
dism /Unmount-Wim /MountDir:"%IP%" /commit

::Mac OS BootCamp will look for autorun.inf in order to validate this disk as a Windows Installer CD
::adding this allows us to start unattended installs using WinPE
date /T > "%PE_TEMP%\WinPE-%PE_ARCH%\media\autorun.inf"

::bootable ISO includes both BIOS & EFI boot loaders
oscdimg -m -o -u2 -udfver102 -bootdata:2#p0,e,b"%PE_TEMP%\WinPE-%PE_ARCH%\fwfiles\etfsboot.com"#pEF,e,b"%PE_TEMP%\WinPE-%PE_ARCH%\fwfiles\efisys.bin" "%PE_TEMP%\WinPE-%PE_ARCH%\media" "%PE_TEMP%\WinPE-%PE_ARCH%\WinPE-40-%PE_ARCH%.iso"
@echo off

::rename the WIM file to avoid having multiple image files on the WDS server with the same filename
ren "%PE_TEMP%\WinPE-%PE_ARCH%\media\sources\boot.wim" boot_%PE_ARCH%.wim

if "%PE_ARCH%"=="x86" set WDS_ARCH=%PE_ARCH%
if "%PE_ARCH%"=="AMD64" set WDS_ARCH=X64
for %%i in (%WDS_SERVERS%) do (
     echo.
     echo Adding/updating boot image on WDS server: %%i
     :: try to add the image first, if that fails then replace existing
     wdsutil /Verbose /Progress /Add-Image /ImageFile:"%PE_TEMP%\WinPE-%PE_ARCH%\media\sources\boot-40-%PE_ARCH%.wim"^
      /Server:%%i /ImageType:Boot /Name:"Microsoft Windows PE 4.0 (%PE_ARCH%)" || wdsutil /Verbose /Progress /Replace-Image^
      /Image:"Microsoft Windows PE 4.0 (%PE_ARCH%)" /ImageType:Boot /Architecture:%WDS_ARCH% /ReplacementImage^
      /Name:"Microsoft Windows PE 4.0 (%PE_ARCH%)" /ImageFile:"%PE_TEMP%\WinPE-%PE_ARCH%\media\sources\boot-40-%PE_ARCH%.wim"^
      /Server:%%i
     echo.
)
::rename the WIM back again so bootable USB devices can be created
ren "%PE_TEMP%\WinPE-%PE_ARCH%\media\sources\boot-40-%PE_ARCH%.wim" boot.wim
echo *******************************************************************
echo WDS boot image(s) updated
echo.
echo A bootable ISO of this image has been created at:
echo   %PE_TEMP%\WinPE-%PE_ARCH%\WinPE-40-%PE_ARCH%.iso
echo.
echo To create a bootable USB key, use diskpart.exe to create a FAT32 partition
echo and mark it active, then copy the contents of this folder to its root:
echo   %PE_TEMP%\WinPE-%PE_ARCH%\media
echo.
echo FAT32 is required for EFI support.
echo.
if "%2"=="nopause" goto :eof
pause
goto :eof

Windows software deployment and update script

For many years I have used scripts of my own design to build workstations and to roll out software updates. At the time I created these I found that most of the tools which could accomplish these tasks were unwieldy. Group Policy software deployment in particular never really seemed fit for purpose since it extended login times so dramatically. My experience gained in a previous job spent packaging applications for deployment had taught me that all installed software populates consistent information in the Windows Registry, so in my current job I tended to audit this data directly via my scripts. This was saved into an SQL database from where it could be queried, or manipulated via a data source in Excel.

I’m working my notice period at the moment ready for a new job I’ll start in October, and so I’m going over the stuff I have created in the current job in order to prepare my handover documents. Mindful of the dependency my current employer has on these custom scripts I decided to get a quote for a Dell KACE solution, thinking that since it’s a Virtual Appliance, and since there are only 150 PCs here it shouldn’t be too expensive – after all it’s only really providing what my scripts already do (workstation builds, drivers, software deployment, and auditing). But here’s the thing – they wanted something like £13,000! (I can’t recall the precise figure). To put it in context this figure is around one third of the cost of replacing all the workstations with new ones, or say half the annual salary of an IT support technician – quite out of the question.

Unsurprisingly I have decided instead to simply tidy up my scripts to make them easier to use. Sure, you could accomplish these tasks with SCCM but that’s not free either. In an SME, why spend huge amounts of money on something that can be automated without much trouble using mechanisms that are built in. Heck, even the uninstall command line is stored in the registry for virtually all software – that’s how the Add/Remove Programs Control Panel works! And most software can be installed silently in the desired way provided you research the command line arguments to do so. It’s no accident that AppDeploy.com which was a great crowdsourced repository of this knowledge became KACE which was then acquired by Dell. It still exists, though the content doesn’t seem to be as well maintained as it was.

I have used a startup script written in VBScript to keep software up to date on workstations. A startup script runs as the SYSTEM account so permissions are not an issue. Since I also maintain an unattended installation I already have a package folder with all the scripts to install each package. All I needed to code was a way to audit the Registry for each package and add some logic around that. Up until now, I had tended to write sections of the script specifically tailored for each package, and from there it’s not much of a stretch to apply packages to a workstation based on its OS version, or Active Directory OU or group membership. For the script I have published below, I have recreated this logic as a single function which can be invoked with a one line entry for each package (see the highlighted part) – everything else is taken care of. I hope it helps someone to save £13,000 :)

 

Sample script output

Running software package check for Adobe Flash Player...
  Registry data found at branch "Adobe Flash Player ActiveX"
  Comparing detected version 11.3.300.271 against desired version 11.4.402.265
  Removing old version 11.3.300.271
    Killing iexplore.exe
    Override detected, running "u:\packages\flash\uninstall_flash_player.exe -uninstall"
    u:\packages\flash\uninstall_flash_player.exe -uninstall
  Installing Adobe Flash Player 11.4.402.265

Running software package check for Paint.NET...
  Registry data found at branch "{529125EF-E3AC-4B74-97E6-F688A7C0F1C0}"
  Comparing detected version 3.60.0 against desired version 3.60.0
  Paint.NET is already installed and up to date.

Running software package check for Adobe Reader...
  Registry data found at branch "{AC76BA86-7AD7-1033-7B44-AA0000000001}"
  Comparing detected version 10.0.0 against desired version 10.1.4
  Removing old version 10.0.0
    Using UninstallString from the Registry, plus "/qb-!"
    MsiExec.exe /I{AC76BA86-7AD7-1033-7B44-AA0000000001} /qb-!
  Installing Adobe Reader 10.1.4

Running software package check for Photo Gallery...
  Registry data found at branch "{60A1253C-2D51-4166-95C2-52E9CF4F8D64}"
  Comparing detected version 16.4.3503.0728 against desired version 16.4.3503.0728
  Photo Gallery is already installed and up to date.

Running software package check for Mendeley Desktop...
  Installing Mendeley Desktop 1.6
 

The script

'startup.vbs
'patters 2006-2012

Option Explicit
Dim objNetwork, objShell, objReg, strKey, colProcess, objProcess, arrSubKeys 
Dim strFileServer
Const HKEY_CURRENT_USER = &H80000001
Const HKEY_LOCAL_MACHINE = &H80000002

'set up objects
Set objNetwork = CreateObject("WScript.Network")
Set objShell = CreateObject("WScript.Shell")
Set objReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\default:StdRegProv")

strFileServer = "YOURSERVERHERE"
MapNetworkDrive "U:","unattended"

Package "flash.cmd", "Adobe Flash Player", "11.4.402.265", "u:\packages\flash\uninstall_flash_player.exe -uninstall", False, True, "iexplore.exe"    
Package "paintnet.cmd", "Paint.NET", "3.60.0", "/qb-!", False, False, "" 
Package "adobe.cmd", "Adobe Reader", "10.1.4","/qb-!",False, False, array("outlook.exe","iexplore")
Package "photogal.cmd", "Photo Gallery", "16.4.3503.0728", "/qb-!", False, False, "iexplore.exe"
Package "mendeley.cmd", "Mendeley Desktop", "1.6", "/S", True, False, "winword.exe"

objNetwork.RemoveNetworkDrive "U:", True, True
WScript.Echo VbCrLf & "Finished software checks"


Function Package(strPackageName, strTargetDisplayName, strTargetVersion, strExtraUninstParams, boolExtraUninstQuotes, boolUninstForceOverride, ProcessToKill)

  '=============================================================================

  'To understand this function you need to know that installed software packages
  'will populate keys below these branches of the Registry:
  '  HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
  '  HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
  '    (the latter for 32bit software on 64bit Windows)
  'This is the data that is mined when you look at Add/Remove Programs
  'in the Control Panel 

  'strPackageName is the package script on your package server (e.g. flash.cmd)

  'strTargetDisplayName can be a full or partial match of the Registry key
  'DisplayName (matches from the left)
  '  "Java(TM)" would match "Java(TM) 6 Update 5" and all other versions

  'strTargetVersion is the full version number from DisplayVersion in the Registry
  'Each decimal point of precision will be compared in turn.

  'If the Registry key DisplayVersion is not used by a package, the same number
  'of digits is parsed from the right hand side of the DisplayName string

  'strExtraUninstParams is used when you want to override the command line
  'specified by QuietUninstallString in the Registry, or for when that value is
  'missing for example, sometimes InnoSetup packages will specify the switch
  '/SILENT in QuietUninstallString, but you may need to override by appending
  '/VERYSILENT to the command line in UninstallString
  'If neither QuietUninstallString and UninstallString are present, the script
  'will use strExtraUninstParams as the full uninstall command line
  
  'Some packages define UninstallString as a long filename but forget to
  'surround it with quotes. You can correct this by setting
  'boolExtraUninstQuotes = True
  '   Package "mendeley.cmd", "Mendeley Desktop", "1.6", "/S", True, False, "winword.exe"

  'In some cases you may want to ignore the value of both QuietUninstallString
  'and UninstallString and override the command completely. To do this, set
  'boolUninstForceOverride to True
  '   Package "flash.cmd", "Adobe Flash Player", "11.4.402.265", "u:\packages\flash\uninstall_flash_player.exe -uninstall", False, True, "iexplore.exe"

  'Finally, ProcessToKill is a string or array containing the name(s) of any
  'running process(es) you need to kill, if plugins are being installed for Word
  'or Internet Explorer for instance.

  '=============================================================================

  Dim arrBranches, strBranch, boolRemoval, strActualDisplayName, strActualVersion
  Dim strQuietUninstall, strUninstall
  WScript.Echo VbCrLf & "Running software package check for " & strTargetDisplayName & "..."
  'we need to iterate through both the 32 and 64bit uninstall branches of the Registry
  arrBranches = Array("SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\", "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\")
  For Each strBranch In arrBranches
    'firstly, remove old version of package if it's present
    objReg.EnumKey HKEY_LOCAL_MACHINE, strBranch, arrSubKeys
    If IsArray(arrSubkeys) Then
      For Each strKey in arrSubkeys
        objReg.GetStringValue HKEY_LOCAL_MACHINE, strBranch & strKey, "DisplayName", strActualDisplayName
        If Left(strActualDisplayName, Len(strTargetDisplayName)) = strTargetDisplayName Then
          'we've found the target software package
          WScript.Echo "  Registry data found at branch """ & strKey & """"
          'is there a version string (not all software will have one)?
          objReg.GetStringValue HKEY_LOCAL_MACHINE, strBranch & strKey, "DisplayVersion", strActualVersion
          If Not IsNull(strActualVersion) Then
          Else
            'if there's no version string we'll try to grab the same number of chars from the right hand side of the DisplayName string  
            strActualVersion = Right(strActualDisplayName, Len(strTargetVersion))
          End If
          If (IsUpgradeNeeded (strActualVersion,strTargetVersion)) = True Then
            strQuietUninstall = ""
            WScript.Echo "  Removing old version " & strActualVersion
            KillProcess ProcessToKill
            'check the package's registry settings
            objReg.GetStringValue HKEY_LOCAL_MACHINE, strBranch & strKey, "UninstallString", strUninstall
            objReg.GetStringValue HKEY_LOCAL_MACHINE, strBranch & strKey, "QuietUninstallString", strQuietUninstall
            If Not strExtraUninstParams = "" Then
              'Extra parameters were sent to the function
              If boolUninstForceOverride = True Then
                'Entire uninstall command line was forced so use strExtraUninstParams, regardless of what's in the Registry
                WScript.Echo "    Override detected, running """ & strExtraUninstParams & """"
                WScript.Echo "    " & strExtraUninstParams
                WinExec strExtraUninstParams
              ElseIf Not IsNull(strUninstall) Then
                'use the basic UninstallString plus the additional parameters
                If boolExtraUninstQuotes = True Then
                  strUninstall = """" & strUninstall & """"
                End If
                strUninstall = strUninstall & " " & strExtraUninstParams
                WScript.Echo "    Using UninstallString from the Registry, plus """ & strExtraUninstParams & """"
                WScript.Echo "    " & strUninstall
                WinExec strUninstall
              Else
                'no UninstallString was found in the Registry, so assume that strExtraUninstParams is the full removal command line
                WScript.Echo "    No UninstallString found, running """ & strExtraUninstParams & """"
                WScript.Echo "    " & strExtraUninstParams
                WinExec strExtraUninstParams
              End If
            Else
              'No extra parameters were sent to the function
              'if there's already a value for QuietUninstallString then use that command line
              If Not IsNull(strQuietUninstall) Then
                WScript.Echo "    Using QuietUninstallString directly from the Registry"
                WScript.Echo "    " & strQuietUninstall
                WinExec strQuietUninstall
              ElseIf Not IsNull(strUninstall) Then
                'no QuietUninstallString was found, fall back to UninstallString
                If boolExtraUninstQuotes = True Then
                  strUninstall = """" & strUninstall & """"
                End If
                WScript.Echo "    Using UninstallString directly from the Registry"
                WScript.Echo "    " & strUninstall
                WinExec strUninstall
              Else
                WScript.Echo "    ERROR - this package doesn't seem to have any UninstallString defined - you'll need to send one to the Package function (see script source for details)"
                Exit Function
              End If
            End If
          Else
            'IsUpgradeNeeded (strActualVersion,strTargetVersion) is False
            'package was detected, but version is >= than the one specified
            WScript.Echo "  " & strTargetDisplayName & " is already installed and up to date."
            Exit Function
          End If
        End If
      Next
    End If
  Next
  'install package
  WScript.Echo "  Installing " & strTargetDisplayName & " " & strTargetVersion
  KillProcess ProcessToKill
  WinExec "U:\packages\" & strPackageName
End Function


Function IsUpgradeNeeded(strVerActual,strVerDesired)
  Dim arrActualVersion, arrDesiredVersion, i
  'Break software version down on decimal points
  arrActualVersion = split(strVerActual,".")
  arrDesiredVersion = split(strVerDesired,".")
  WScript.Echo "  Comparing detected version " & strVerActual & " against desired version " & strVerDesired
  'iterate, comparing each sub-version number starting from left
  For i = 0 To UBound(arrActualVersion)
    'WScript.Echo "  comparing digit... is " & arrActualVersion(i) & " less than " & arrDesiredVersion(i) 
    If arrActualVersion(i) < arrDesiredVersion(i) Then
      'installed version is out of date
      IsUpgradeNeeded = True
      Exit Function
    ElseIf arrActualVersion(i) > arrDesiredVersion(i) Then
      'installed version is newer
      IsUpgradeNeeded = False
      Exit Function     
    End If
  Next
  'thus far the version numbers are the same, but there may be additional
  'decimal points of precision in the desired version
  '  e.g. Adobe Reader 10.1.4 is newer than 10.1
  If UBound(arrDesiredVersion) > UBound(arrActualVersion) Then
    IsUpgradeNeeded = True
  Else
    IsUpgradeNeeded = False
  End If
End Function


Function MapNetworkDrive(strDriveLetter, strSharePath)
  On Error Resume Next
  'if the share name is not a UNC path, assume it's on the normal fileserver
  If Not Left(strSharePath,2) = "\\" Then
    strSharePath = "\\" & strFileServer & "\" & strSharePath
  End If
  If objFSO.DriveExists(strDriveLetter) Then
    objNetwork.RemoveNetworkDrive strDriveLetter, True, True
  End If
  objNetwork.MapNetworkDrive strDriveLetter, strSharePath
  If Err.Number <> 0 Then
    WScript.Echo "Error - " & Err.Description
    Err.Clear
  End If
  On Error Goto 0
End Function


Function WinExec(strExec)
  Dim objExec, eTime
  WinExec = True
  Set objExec = objShell.Exec(strExec)
  eTime = DateAdd("s", 120, Now)
  Do While objExec.Status = 0
    WScript.Sleep 1000
  Loop
End Function


Function KillProcess(Process)
  Dim strProcessElement
  If IsArray(Process) Then
    For Each strProcessElement in Process
      KillIndividualProcess(strProcessElement)
    Next
  ElseIf Not Process = "" Then
    KillIndividualProcess(Process)
  End If
End Function


Function KillIndividualProcess(strProcess)
  Dim colProcess, objProcess
  Set colProcess = objWMI.ExecQuery("Select * from Win32_Process")
  For Each objProcess in colProcess
    If LCase(objProcess.Name) = LCase(strProcess) Then
      WScript.Echo "    Killing " & strProcess
      'occasionally one parent process may kill all children leading to an object error
      'so disable error handling temporarily
      On Error Resume Next
      objProcess.Terminate()
      On Error Goto 0
    End If
  Next
End Function

Deploying Windows Photo Gallery 2012

Windows-Photo-Gallery

Though it seems primarily pitched at home users, Microsoft’s Windows Photo Gallery is a useful image management tool even in a professional environment. It’s distributed as part of a suite of software known collectively as Windows Essentials 2012. I don’t understand why these tools aren’t included in Windows itself, but since they were until recently part of the Live family I’m presuming that they were designed to encourage the use of Microsoft’s online services. The apparent home user bias to the setup (a single installer for the whole suite, which downloads on demand, which asks for a Live sign-in, and which alters homepage and search provider) consequently makes Photo Gallery quite difficult to deploy and automate.

Firstly the proper offline installer package is tucked away here on Microsoft’s website.

The next issue is that the silent install switches don’t seem to be officially documented by Microsoft. I was able to piece together the working command line using a TechNet forum post, this blog post about deploying the 2011 version, and some stuff on the MSFN forum.

What held me up for a while is that you can no longer target only the Photo Gallery app – MovieMaker and Photo Gallery are bundled together with 2012. So I arrived at this one-liner which I invoke from a more complex workstation startup script, if it’s needed:

start /wait WLSetup-all.exe /q /r:n /NOToolbarCEIP /NOhomepage /nolaunch /nosearch /AppSelect:MovieMaker /log:%TEMP%\WLEsetup.log

The HKCU registry customizations are pretty much the same as for the 2011 version, so to suppress the EULA and Microsoft account sign-in prompt, and to prevent nags about file type associations you will need to set the following in your login script (this is an extract from my VBScript one, but it’s pretty human-readable):

...
'default preferences for Microsoft Photo Library (agree EULA, don't steal filetype associations, no Windows Live sign-in)
objReg.CreateKey HKEY_CURRENT_USER,"Software\Microsoft\Windows Live"
objReg.CreateKey HKEY_CURRENT_USER,"Software\Microsoft\Windows Live\Common"
objReg.SetStringValue HKEY_CURRENT_USER,"Software\Microsoft\Windows Live\Common","TOUVersion","16.0.0.0"
objReg.CreateKey HKEY_CURRENT_USER,"Software\Microsoft\Windows Live\Photo Gallery"
objReg.SetDWORDValue HKEY_CURRENT_USER,"Software\Microsoft\Windows Live\Photo Gallery","SignInRemindersLeft","0"
objReg.CreateKey HKEY_CURRENT_USER,"Software\Microsoft\Windows Live\Photo Gallery\Library"
arrStringValues = Array(".WDP",".BMP",".JFIF",".JPEG",".JPE",".JPG",".PNG",".TIF",".DIB",".TIFF",".ICO")
objReg.SetMultiStringValue HKEY_CURRENT_USER,"Software\Microsoft\Windows Live\Photo Gallery\Library", "DontShowAssociationsDialogExtensions", arrStringValues
...

To install on a Windows 8 workstation you’ll need the .Net Framework 3.5 “feature” to be installed, which isn’t there by default (Control Panel > Programs > Uninstall a program > Turn Windows Features on or off). This is problematic if you’re using WSUS – the attempt to download the update will fail with error 0x800f0906. Microsoft have an MDSN article about this, but the prescribed fix of using DISM to fetch the feature from the install media didn’t work for me on Windows 8 Enterprise. I had to remove my PC from an OU which inherits WSUS settings, run gpupdate /force then try again, this time successfully.

In my organization, the requirement for Photo Gallery is for users to interact with a centralized image library. This is stored on a Window 2008 R2 server, and I discovered that I could not add this folder to the Pictures library unless it was indexed on the server side (well, without enabling offline folders – which I don’t want). The relevant information on this topic can be found in this Technet post. In summary, you need to enable the Windows Search Service on the file server, which is a “Role Service” under the File Services role in Server Manager.

The missing piece of the puzzle so far is how to programmatically add this image repository location to each user’s Pictures library. I found a page about this, though the tools did not seem to actually work. Admittedly it’s a few years old, so maybe there are some more official tools now. More research to follow…